重建我的作品集:為什麼我離開了 Next.js

· 中文 · Claude Sonnet 5 代筆

articleastromigrationperformance

我原本的作品集是一個 Next.js 應用程式。它運作得很好,但整個網站——一個靜態的自我介紹、一份證照列表、幾個作品展示——卻在每一次造訪、每一個頁面,都把 React 的 hydration runtime 送給訪客,不管那個頁面實際上到底需不需要任何前端互動性。而事實上,幾乎沒有一個頁面需要。

預設零 JavaScript

Astro 的設計把預設值反過來了:元件預設只會渲染成純 HTML,除非你明確用 client: 指令,把頁面中的某個部分選擇性地交給前端 JavaScript 處理。這個網站唯一真正需要在瀏覽器裡執行的部分,是 AI 聊天小工具,所以也只有它會被打包成一份 JavaScript:

---
import { ChatWidget } from '../components/ai/ChatWidget';
---

<ChatWidget client:idle />

client:idle 會在瀏覽器閒置時才進行 hydration,所以它永遠不會跟頁面的初次渲染搶資源。頁面上其他所有東西——自我介紹、證照卡片、下方選單——都是建置時期就產生好的靜態 HTML,執行期完全沒有額外成本。

用 Content Collections 取代手刻的資料檔

證照、作品展示,以及現在的部落格文章(包含這一篇),都是型別化的內容集合(content collections),會在建置時期依照 Zod schema 驗證:

const blogSchema = z.object({
  title: z.string().min(1),
  date: z.coerce.date(),
  tags: z.array(z.string()).default([]),
  summary: z.string().min(1),
});

如果我在某篇文章的 frontmatter 忘了填必填欄位,建置會立刻失敗、並給出明確的錯誤訊息——而不是三個頁面之後才冒出一個執行期的 undefined bug。

不需要獨立 API 層的伺服器端邏輯

唯一真正動態的功能——AI 聊天機器人——需要一個真正的後端:速率限制、加密、呼叫 Anthropic 的 API。在舊的 Next.js 專案裡,這代表要用 Server Action。在 Astro 裡,Actions 扮演一樣的角色:這是一組只在伺服器端執行的型別化 RPC 函式,瀏覽器可以直接呼叫,不需要手刻一個 REST 端點,也不會把任何機密洩漏到前端程式碼裡。

這次遷移不只是換一個框架而已——也是我順手修掉一些一直想修的問題的機會(一個在多實例間也能正確運作的速率限制器、一個不依賴第三方加密函式庫的混合式加密實作)。這兩個部分我另外寫了文章介紹。