iT邦幫忙

2026 iThome 鐵人賽

DAY 1
0

如果你已經會 Vue、Nuxt,也碰過 React 或 Next,聽到「內容站可以用 Astro」,會先想問兩件事:這跟我已經會的框架到底差在哪?做 SEO 用 Nuxt 不就夠了?

Astro 跟 Nuxt 各適合什麼網站?

部落格、文件站和行銷頁以內容為主,重點是文字、圖片,以及搜尋引擎讀得到的 HTML,適合用 Astro。互動多、還要跨頁共享狀態的網站,Nuxt 會更合適。接下來 30 天會用同一個內容站,讓你用實作回答三個問題:哪些頁面維持靜態、哪些區塊做成 island、哪些功能交給 server。

實作步驟

對照組有三個內容相同的頁面,用來比較 Astro 與 Nuxt 的 JavaScript 傳輸量:第 1 頁是 Astro 靜態頁;第 2 頁是 Astro 的 Vue
island,按鈕使用 client:load;第 3 頁是 Nuxt。

前兩頁的程式碼放在 app-steps。這個 repo 一天一個 commit,Day N 對應
step-N,每篇文章末尾都會標出當天的 commit,所以你可以只看某一天改了哪幾行,不必從完整專案回推。Day 1 用的是
step-01

git clone https://github.com/stevecyj/app-steps.git
cd app-steps && git checkout step-01
npm ci && npm run build                     # 產出 dist/client
python3 -m http.server 4321 --directory dist/client
# 無痕開::4321/bench/(JS 0 個)、:4321/bench/island/(JS 3 個)

第 1 頁的路徑是 /bench,第 2 頁是
/bench/island。用 Chrome 無痕模式開啟頁面,避免擴充套件注入腳本。接著在 DevTools 的 Network 面板選擇 JavaScript
filter,底部狀態列的 request/transferred 會顯示 JavaScript 傳輸量。

第 3 頁的 Nuxt 對照組不在那個 repo 裡,要自己建一個:執行 npm create nuxt@latest nuxt-bench(template 選
minimal),把 app/app.vue 換成跟前兩頁同一篇內容,並放入同樣的 <ReactionButton />。內容必須跟前兩頁逐字一致,量出來的差異才是框架造成的。建好後這樣量:

cd nuxt-bench && npm install && npm run generate   # 產出 .output/public(是 generate 不是 build)
python3 -m http.server 4322 --directory .output/public
# 無痕開::4322/(JS 1 個)

在 Network 面板點 JS filter,會看到 1 個 chunk(整頁的 JS)。量測要用 generate 產出的正式版。npm run dev
會帶 HMR,而且沒有壓縮,量出的 JS 會比上線版本多。Nuxt 的 npm run build 會產出 Node 伺服器,.output/public
裡沒有 index.html,無法用靜態 serve 開啟頁面;Astro 的 build 才會直接產出靜態站。

表格中的 JS 大小是在 npm run build
後用無痕模式從 Network 讀取;FCP 是 Lighthouse 行動版各跑 3 次取中位數。raw 是 Network 顯示的未壓縮值,括號內是發佈上線的 gzip:

頁面 JS 檔數 JS 傳輸 raw / gzip FCP
第 1 頁 Astro 無島 0 0 B 627 ms
第 2 頁 Astro Vue 島 3 73.2 KB / ~29 KB 921 ms
第 3 頁 Nuxt 1 129 KB / ~49 KB 1353 ms

Lighthouse 行動版 perf 在三頁都是 100。頁面很小,perf 分數看不出差異,但 FCP 看得出來。

第 1 頁 Astro 沒有 island:Network JS filter 顯示 0 個 script、0 kB。

第 1 頁 Astro 無島的 DevTools Network 面板,JS filter 下底部狀態列顯示 0 個 script、0 B

第 2 頁 Astro Vue island:3 個 script。這頁原本只有 HTML 和 CSS,想讓按鈕能互動,就得再載入三個 JS:

  1. Vue 本身。
  2. 那顆按鈕。
  3. 找到按鈕、接上 Vue,讓它能互動。

第 2 頁 Astro Vue 島的 Network 面板,JS filter 下顯示 3 個 script(ReactionButton/client/runtime-core)、共 73.2 kB

第 3 頁 Nuxt:1 個 chunk、129 kB(整頁的 JS)。

第 3 頁 Nuxt 的 Network 面板,JS filter 下顯示 1 個入口 chunk、129 kB

(三張都是未壓縮的 raw 值,跟上表的 raw 欄一致;上線經 gzip 後約 0 / 29 / 49
KB。判讀時看底部狀態列「斜線前面」的數字。)

為什麼 Astro 要這樣設計

同一份內容、同一顆按鈕,在 Astro 無 island 時是 0 JS,Nuxt 則載入整頁的 JS。差異來自兩個框架的預設執行方式。

Astro 預設把每一頁當成一份印好的畫面:純 HTML 和 CSS 送到瀏覽器,不帶 JavaScript。需要互動的區塊才加上
client:load,瀏覽器只會收到該區塊需要的 JS;未指定的地方維持零 JS。

Nuxt 預設把整頁交給 Vue 接管。頁面不論有沒有東西要按,瀏覽器開啟時都會先載入整頁的 Vue。因此在 Nuxt 裡放同一顆
<ReactionButton />,不必加 client: 就能互動。

預設無 JS 對內容優先的網站有利:使用者更快看到能讀的東西,低速網路也撐得住,爬蟲取得的則是完整 HTML,對 SEO 有直接幫助。如果網站需要大量互動、跨畫面共享狀態,或 SPA 不換頁的過場,逐塊指定會限制實作方式,這類網站更適合由 Nuxt 接管整頁。

註:這段提到的預設行為,查證基準是 Astro 官方 islands 文件(Astro
v6)與 Nuxt 官方 rendering 文件(本文撰寫時為 Nuxt 4)。

常見錯誤與除錯

照上面步驟量測,如果 Network 還是出現 JS,通常要先檢查量測環境。

本文的「0 JS」表示 Script 數量是 0。內容站仍會下載 HTML、圖片、favicon。因此 Network 面板要先點 JS filter,只看 script;底部的「X / Y
requests」是「篩選後 / 全部」,斜線前面的數字才是 JS 數量。

如果 JS filter 仍顯示一堆 JS,先排除以下兩種與「Astro 請求了多少 JS」無關的腳本來源。

1:在開發模式下量。用 npm run dev
啟動的網站不會是 0 JS。開發模式為了支援存檔後自動更新與開發工具列,會載入一批只在本機執行的 JS,上線版本不會有這些。量測時要看
npm run build 產出的版本,不要看 dev。

2:使用一般瀏覽器,量到擴充套件的腳本。瀏覽器裝了擴充套件時,Network 可能多出幾支不是網站本身的 JS 檔,像
detector.jscontent_main.jsjs.jsdom.js
。無痕模式預設停用擴充套件,能避免把這些檔案算進網站的 JS。

確認這兩件事(看 build 出來的產物、開無痕模式),再 Network 點 JS filter、看底部狀態列,數字才算得準。

這組數字能說明什麼?

這組量測有兩類數字可比較。JS 的結果是第 1 頁為 0,第 2 頁比第 3 頁少;Lighthouse 的 FCP 則是第 1 頁最快、第 2 頁其次、第 3 頁最慢。我這次量到的 gzip 是 0、29、49
KB,FCP 是 627、921、1353 ms,performance 分數則三頁都是 100。你量到的數字可能不同,順序應該接近。

只看 Lighthouse 的 100 不夠。小頁面很容易一排 100,但 FCP 還是會差一截。內容站的頁面越多、互動越少,少送一點 JS 的影響才會慢慢放大。反過來,如果你的站本來就很多互動、很多狀態要一起跑,只看這組 JS 和 FCP,還不夠讓你把 Nuxt 排除掉。

你的站如果明顯偏內容站,這次對照至少說明,同一份內容都用預設做法時,Astro 送出去的 JS 比較少,FCP 也比較快。

先比較 Vue /
Nuxt,兩者屬於同一條框架技術線。加入 React 和 Next 後,可以用同一篇內容、同一顆按鈕檢查這是不是 Vue 特例。React
/ Next 在這裡用來排除框架偏誤,系列仍以 Vue / Nuxt 為主。七種做法跑同一輪 Lighthouse(行動版、各 3 次取中位數):

做法 內容在 HTML JS 檔數 JS 傳輸 raw / gzip FCP
Astro 無島 0 0 B 627 ms
Astro Vue 島 3 73 KB / ~29 KB 921 ms
Astro React 島 3 195 KB / ~62 KB 1295 ms
Vue SPA 1 62 KB / ~25 KB 1051 ms
React SPA 1 193 KB / ~61 KB 1651 ms
Nuxt 1 129 KB / ~49 KB 1353 ms
Next 6 514 KB / ~146 KB 632 ms

純 SPA 的 JS 其實最小,Vue SPA 才 25
KB,但內容不在 HTML。文章是 JavaScript 畫出來的,HTML 打開是空的,爬蟲和還沒跑完 JS 的人看到一片空白,FCP 也被拖到 1051
ms,比 Astro 無島的 627 ms 慢一截。內容站第一個要求就是內容要在 HTML 裡。

把能達到 SEO 效果的做法放在一起比較(Astro 島、Nuxt、Next),Astro 把 JS 綁在島上,沒島的頁就是 0;Nuxt 和 Next 不管有沒有要互動,都先送一份整頁的框架。Vue 與 React 都有相同結果。

把內容能出現在 HTML 的幾種做法抽出來,照 JS 由小到大排:

做法 JS 檔數 JS 傳輸 raw / gzip FCP
Astro 無島 0 0 B 627 ms
Astro Vue 島 3 73 KB / ~29 KB 921 ms
Nuxt 1 129 KB / ~49 KB 1353 ms
Astro React 島 3 195 KB / ~62 KB 1295 ms
Next 6 514 KB / ~146 KB 632 ms

最小的兩個都是 Astro,沒 island 的頁面是 0。

內容是否在 HTML,取決於框架的使用方式。

我量過一個上線中的多語系品牌官網,它用的就是 Astro。它每一頁都是一個 .astro 外殼,裡面掛幾個 Vue 元件、全部加
client:load,文案和圖片都寫在那些 Vue 元件裡。build 出來,首頁 HTML 只有 7.1 KB,首頁要載的 JS 是 210 KB。

這個 Astro 專案的結果對應上表 Vue
SPA 那一格,內容不在 HTML,要等 JS 跑完才出現。團隊原本使用 Vue,把整個 view 掛上來需要的改動最少。要拿到前面量到的好處,還得判斷哪些內容不必是 JS,光是選擇 Astro 並不夠。

要拿到上面那組數字,必須只讓真正需要互動的區塊載入 JS。

下一步

內容優先的網站可以把 Astro 納入選項,並先訂出一個驗收標準:文章頁預設輸出完整 HTML、0
JS;只有真正需要互動的元件才變成 island。建立 Astro 專案後,就能用這項標準逐頁檢查。

本日程式碼:step-01


下一篇
建立 Astro 專案時,Node 與套件版本要怎麼管理?
系列文
用 Astro 打造 Content-first 前端網站:30 天從靜態內容到會員、資料庫與選型3
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言