2026 年 10 月 25 日,我讓 AI coding agent 把 aron.tw 從 Next.js 改成 SvelteKit,上線後又改成 Astro。讀者看到的網站大致一樣,搜尋、分類、語言切換也都還在。搜尋頁的外部 JavaScript 卻從 155.7 KB 降到 15.7 KB。

這是這次重構最直接的收穫。網站沒有少掉我想保留的功能,瀏覽器需要下載和執行的程式少了很多。

我想藉這個專案看看,一個主要供人讀文章的網站,換掉執行模型之後,能省下多少工作,又會留下什麼。當時網站有五種語言、35 份文章語言版本、約 60 個靜態頁面,部署在 Cloudflare Pages。互動包括 Pagefind 搜尋、分類篩選、複製按鈕、主題與語言選單,以及首頁的人像滑桿。

這些互動可以一塊一塊分開處理。搜尋條件改變,不需要連帶更新文章頁的選單;讀完一篇換到下一篇,也沒有一份編輯到一半的表單要跟著走。這是我願意試 Astro 的重要條件。

遷移沿用既有的行為測試。Astro 與 SvelteKit 的 210 組靜態及互動截圖,比對結果是零像素差異;約 223 個 E2E 的驗收規格則保留下來,調整少數與框架實作綁定的案例。這些紀錄讓我能把「功能有沒有留下來」和效能數字分開檢查。

HTML 已經有了,瀏覽器還在忙什麼?

三版都是靜態部署,都會送出 HTML。差別在 HTML 之後。

Next.js 在建置時執行 Server Components,除了 HTML,也準備客戶端換頁使用的 payload;Client Components 需要 hydration 接上互動。SvelteKit 同樣會 hydrate 頁面,並由 client router 接手站內換頁。這些機制讓元件狀態與導覽能在瀏覽器裡延續。Next.js 靜態匯出、SvelteKit 頁面選項

本站的 Astro 版把文章在建置時處理完,選單、篩選等功能交給普通 TypeScript 腳本。沒有 React 或 Svelte 在瀏覽器裡接手整頁,換頁則載入下一份 HTML。

搜尋頁最適合拿來對照,因為三版都包含同一套 Pagefind,沒有把搜尋功能拿掉來換小數字。

搜尋頁的外部 JavaScript

aron.tw · 2026-10-25

這份 Astro 實作比原 Next.js 版少了約 90% 的外部 JS。

KB
  • Next.js155.7
  • SvelteKit60.6
  • Astro15.7
查看搜尋頁 JS 數據
搜尋頁的外部 JavaScript · KB
框架KB
Next.js155.7
SvelteKit60.6
Astro15.7

本機同輪比較,數值為 Brotli 壓縮後的外部 Script response body,三版皆含 Pagefind;不含 HTML 內嵌腳本,因此不是全部 JS 傳輸量。取保存的首筆樣本,KB 以 1,024 bytes 計算。

量測摘錄與方法

一般閱讀頁的 Astro 外部 JS 更少,這輪各頁約為 0.9–3.2 KB,搜尋頁另計。不過,Astro 也把部分小腳本內嵌在 HTML 裡,不能看見很小的外部檔案,就把它理解成沒有 JavaScript。

瀏覽器執行的工作也有減少。這輪 Chromium 測試使用 390 px 行動視窗、4 倍 CPU 降速,各頁跑五次取中位數:

各頁中位數的範圍Next.jsSvelteKitAstro
Script 執行時間47–88 ms14–35 ms4–14 ms
JS heap3.1–4.2 MB1.6–2.6 MB0.8–0.9 MB

範圍來自不同頁面,並非同一頁的誤差區間;heap 也只描述 JavaScript 記憶體。對這個部落格來說,少送程式、少執行程式、少保留 JS 記憶體,都是值得留下的改善。

Lighthouse 的分數卻幾乎沒動。另一組 Lighthouse 13.5.0 行動模擬中,每頁三次取中位數,FCP 仍約 3–12 秒;三版共同載入的 11 個同步字型 stylesheet,仍卡在首次繪製的路徑上。

因此下一步若要讓文字更早出現,我會去處理字型。框架已經省掉一部分工作,剩下的瓶頸也變得很具體,沒有必要再換一次框架來回答同一個問題。

互動沒有消失,只是換了寫法

SvelteKit 那一段遷移,留下兩個很有用的案例。

第一個是文章已經在 HTML 裡,hydration payload 又帶了一份。專案最後搭配 transport hook 和自己的輸出處理,把首次載入的重複內文移掉,同時保留客戶端換頁需要的資料。這是本站額外做的處理,不是關掉一個選項就自動完成。

第二個是 head 裡約 18 個高優先度 modulepreload。在當時的慢速 4G 模擬裡,它們與字型競爭頻寬,讓 FCP 多了 0.6–0.9 秒;調成 fetchpriority="low" 後才回到原來的水準。bundle 小了,載入順序仍然會影響讀者先看見什麼。

Astro 版不用重建這套 hydration 狀態,文章 payload 的重複問題也就不必再處理。但搜尋與分類篩選還是要有人寫。

這次的做法是把文章卡片先渲染進 <template>。篩選條件改變時,腳本複製符合條件的卡片、重排列表、更新分頁和網址。對話框、選單和人像滑桿也各自有腳本。遷移完成時,這些互動共八個 TypeScript 模組、860 行。

遷移紀錄裡還有一個很實際的問題:元件腳本曾隨 <template> 裡的內容輸出,留在不會自動執行的模板中。當時把選單腳本移到外層載入,才讓事件確實接上。這類問題會讓人重新注意到腳本放在哪裡;沒有 hydration,也還是得把這些位置安排好。

我接受這些程式碼,因為目前每塊互動都能分開理解。將來若搜尋開始包含編輯、跨區塊連動或大量共享狀態,我不會只為了維持「純 TypeScript」就繼續手動同步所有 DOM。Astro 仍能在需要的地方放進 Svelte 或 React island,到時再比較那一塊的複雜度。

冷建置也短了,但暖建置是另一回事

瀏覽器以外,冷建置從 5.81 秒縮到 2.55 秒,也是這次確實量到的收益。

同一網站的冷建置時間

aron.tw · 2026-10-25

這輪 Astro 冷建置比原 Next.js 實作約少 56%。

秒
  • Next.js5.81
  • SvelteKit3.88
  • Astro2.55
查看冷建置數據
同一網站的冷建置時間 · 秒
框架秒
Next.js5.81
SvelteKit3.88
Astro2.55

Apple M3 Max,同輪交錯、各三次中位數。冷建置清除框架快取與輸出,保留共用 OG 圖快取;計入內容處理與 Pagefind,並非純框架編譯時間。

量測摘錄與方法

如果接著再建置一次,Next.js 的持久快取會縮小差距。這輪暖建置為 Next.js 2.98 秒、SvelteKit 4.07 秒、Astro 2.63 秒。冷建置的改善可以成立,也不用把它延伸成任何建置條件下都快一倍。

這裡我沒有再用程式碼行數或套件數替框架排名。三份實作交給框架與自己維護的工作不同,行數不能直接換算成工時。860 行互動程式值不值得繼續自己管,最後還是要在後續修改時才知道。

換頁那組數字,需要把解讀改回來

這次紀錄裡,換頁比外部 JS 更容易讀錯。

本機從部落格列表點進文章,三版的流程時間如下:

本機:從列表點進文章

aron.tw · 2026-10-25

三版的自動化換頁流程時間接近;Astro 使用完整文件導覽。

ms
  • Next.js249
  • SvelteKit244
  • Astro290
查看本機換頁數據
本機:從列表點進文章 · ms
框架ms
Next.js249
SvelteKit244
Astro290

Apple M3 Max,1280 × 900 桌機視窗、4 倍 CPU 降速,五次中位數,無額外網路降速。計時包含自動化點擊與 URL/標題等待,不是 INP,也不是精確的點擊到繪製時間。

量測摘錄與方法

另一組正式站紀錄則是 SvelteKit 373 ms、Astro 768 ms,各跑三次取中位數。這個差距曾讓換頁成為討論重點,但兩份資料來自不同部署時段,CDN 與網路狀態不同,測試等待的生命週期也不同。Playwright 的 locator.click() 會等待它觸發的導覽,完整文件載入和 client router 更新畫面不能直接當成相同終點。

原始紀錄還曾把約 50 個資源事件解釋成大量 304 驗證。這個解釋需要更正:requestfinished 也會計入從 browser cache 取出的資源。沒有逐筆回應與快取來源,光靠事件數,算不出多少次網路往返。

2026 年 10 月 25 日保存的正式站核對結果,是 HTML 採 max-age=0, must-revalidate,字型與抽查圖片有四小時 freshness,帶 hash 的 /_astro/ CSS、JS 則有一年 immutable。四小時內仍新鮮的字型,不會只因為有 must-revalidate 就每次都上網驗證;這個指令約束的是快取過期後的重用。HTTP 快取規格

所以,完整文件導覽確實要重新解析 HTML、建立 DOM,CSS、字型與圖片卻可以從瀏覽器快取拿。CDN 命中又是另一件事:它讓回應靠近使用者,瀏覽器仍然要發出請求。要看換頁卡在哪裡,這幾種情況必須分開。

本站也開了 hover prefetch。依 Astro 7.4.2 的實作,支援時會用 <link rel="prefetch"> 提前抓下一份 HTML;這不等於下一頁所有資源和互動都已經準備好。當時游標停留 300 ms 的測試沒有改善流程時間,只能描述那次測試,不能據此判定 prefetch 沒有用途。

若接下來要比較換頁體驗,我會重新固定冷/暖 browser cache、確認預載是否完成,再以相同的可見內容時間點量測。現有資料已經足夠確認 JS 與建置的改善;換頁的問題值得另外做對。

這個部落格,我會留在 Astro

這次的網站以閱讀為主,互動也能局部分開。Astro 讓頁面在建置時完成,瀏覽器只接手需要的控制項,這和我目前要維護的東西很接近。

我不會把這個選擇直接搬到每個專案。若頁面之間要保留大量操作狀態,或好幾塊介面經常一起更新,Svelte 的響應式元件與客戶端導覽就有具體用途。SvelteKit 也能用 csr=false 關閉 hydration 與 client router,但原本依賴 Svelte 的互動必須另行安排。

已有 Next.js 專案,則可以先檢查 client boundary 與路由分包。本站沒測 Server Actions、串流等能力,這次重寫也沒有證明換框架比整理原本的實作更划算。AI 和既有測試讓我有條件做這次實驗,並不代表每個團隊都值得投入同樣的遷移。

對 aron.tw,現在的選擇已經夠清楚了:留下 Astro,把那幾塊互動維護好,再處理仍擋著文字出現的字型。框架先選到這裡,網站還有別的事要做。