前言

Nuxt.js 是 Vue 生態系中最成熟的全端框架,它解決了一個根本性的矛盾:現代 SPA(Single-Page Application)帶來了卓越的用戶體驗,卻犧牲了 SEO 可見性與首屏載入效能。Nuxt 透過伺服器端渲染(SSR)和靜態站點生成(SSG)兩種策略,讓開發者能夠在不妥協互動體驗的前提下,獲得接近傳統多頁面網站的 SEO 效果。

深入理解 Nuxt 的生命週期,是充分發揮其 SSR 優勢的關鍵。很多開發者在遷移到 Nuxt 時,因為對執行環境(伺服器端 vs 客戶端)判斷不準確,導致頁面出現水合錯誤、SEO 元數據遺失或資料重複請求等問題。本文將從底層執行機制出發,帶你建立正確的心智模型。

什麼是 Nuxt?

Nuxt.js 是一個基於 Vue.js 的全端框架,提供了服務端渲染(SSR)、**靜態站點生成(SSG)單頁應用(SPA)**等多種渲染策略。

在 Nuxt 3 中,底層引擎由 Nitro 驅動——這是一個跨平台的伺服器引擎,能夠將 Nuxt 應用部署到 Node.js、Bun、Deno 乃至 Cloudflare Workers 等不同運行環境。這種抽象層讓 Nuxt 應用的部署靈活性遠超傳統框架。

Nuxt 的核心優勢

  1. 服務端渲染(SSR):首次請求由伺服器生成完整 HTML,搜索引擎可直接爬取,同時提升首屏渲染速度(FCP)
  2. 靜態站點生成(SSG)nuxt generate 在建置時預渲染所有頁面,生成靜態 HTML 文件,適合內容更新頻率低的網站
  3. 自動路由pages/ 目錄下的文件自動映射為路由,消除手動維護路由表的負擔
  4. 模組化架構:豐富的官方模組(@nuxtjs/i18n@nuxt/image 等)提供即插即用的功能擴展
  5. 同構渲染:相同的 Vue 組件能在伺服器和客戶端兩端運行,消除傳統 SSR 中維護兩套代碼的困境

Nuxt 生命週期詳解

完整生命週期流程

要理解 Nuxt 的生命週期,首先需要建立「雙環境執行」的思維模型。在 SSR 模式下,每一個頁面請求都會同時涉及伺服器端和客戶端兩條執行路徑。

sequenceDiagram
    participant B as 瀏覽器
    participant S as Nitro 伺服器
    participant V as Vue 渲染引擎

    B->>S: GET /page 請求
    S->>S: 執行 Server Plugins
    S->>S: 執行 Server Middleware
    S->>V: 呼叫 setup() + useAsyncData()
    V-->>S: 返回伺服器端 HTML
    S-->>B: 回傳完整 HTML + 狀態快照

    Note over B: 瀏覽器解析 HTML,搜尋引擎可完整爬取

    B->>B: 執行 Client Plugins
    B->>B: Vue 水合(Hydration)— 接管靜態 HTML
    B->>B: onMounted() 等客戶端鉤子執行

    Note over B: 頁面轉為可互動的 SPA

1. 應用初始化階段

當 Nuxt 應用啟動時,nuxt.config.ts 是整個流程的入口。Nuxt 在這個階段完成三件關鍵工作:

模組(Modules)初始化:Nuxt 模組本質上是在建置時執行的函數,它們可以修改 Vite/webpack 配置、注入全局組件、擴展 TypeScript 類型定義。模組的執行順序與 modules 陣列中的順序嚴格對應——這在多個模組相互依賴時尤為重要。例如 @nuxtjs/i18n 必須在任何依賴其路由的模組之前初始化。

插件(Plugins)執行模型:插件按檔案名稱的數字前綴順序執行(例如 1.auth.ts 早於 2.analytics.ts)。插件的適用範圍由後綴決定:

  • .server.ts:僅在伺服器端執行,適合環境配置、資料庫連線等伺服器專屬邏輯
  • .client.ts:僅在客戶端執行,適合 localStorage、第三方分析腳本等瀏覽器 API
  • 無後綴:在兩端都執行,適合全局狀態初始化、API 客戶端配置等通用邏輯

這個命名規範解決了一個常見問題:如果你在 .server.ts 插件中寫了 window.xxx,伺服器端會拋出錯誤;反之在 .client.ts 中直接讀取環境變數也不安全。後綴讓執行環境的邊界一目了然。

運行時配置(Runtime Config):Nuxt 將配置分為私有(runtimeConfig.apiSecret)和公開(runtimeConfig.public.apiBase)兩類。私有配置僅在伺服器端可用,通過 useRuntimeConfig() 在伺服器端存取;公開配置則序列化後傳遞至客戶端,在兩端都可存取。這個設計避免了敏感資料洩漏到客戶端 JavaScript bundle 中。

2. 服務端渲染階段

當瀏覽器發出頁面請求時,伺服器端的渲染流程依序展開:

中間件(Middleware)執行:Nuxt 中間件在路由解析後、組件渲染前執行。伺服器端中間件(middleware/*.server.ts)通常用於認證驗證、重定向、請求日誌等。重要的是,伺服器端中間件每次請求都會執行,不像客戶端中間件只在路由切換時觸發——這對效能敏感的邏輯(如資料庫查詢)需要特別注意。

組件 setup() 執行:Vue 的 setup() 函數(包括 <script setup> 的頂層代碼)在伺服器端同步執行。這意味著所有在 setup() 中進行的非同步資料獲取,都需要使用 Nuxt 提供的資料獲取工具,而非直接使用原生 fetch()

資料獲取的三種模式,彼此定位不同:

useFetch() 是最常用的選擇,它封裝了 $fetch 並自動整合 SSR 資料傳遞機制。在伺服器端獲取資料後,Nuxt 會將資料序列化為 JSON 嵌入回傳的 HTML 頁面中(稱為 payload hydration)。客戶端接管時直接讀取這份資料,無需重複發送請求——這是避免資料「雙重請求」的關鍵機制。

useAsyncData() 提供更精細的控制:自定義快取 key、觸發條件(watch)、資料轉換(transform)、懶加載(lazy: true)等。當你需要整合不是來自 HTTP API 的資料源(例如直接查詢資料庫的 Server Routes),或需要精確控制重新獲取時機時,useAsyncData() 是更好的選擇。

$fetch() 是最底層的 HTTP 客戶端工具,適合用在 Server Routes(server/api/*.ts)中,或在 useAsyncData() 的回調函數中。直接在組件 setup() 頂層使用 $fetch() 是常見錯誤——它不會自動注入 payload 機制,導致客戶端水合時重複請求。

3. 客戶端水合階段

水合(Hydration)是 SSR 架構中最精妙也最容易出錯的環節。理解它的工作原理,是避免 Hydration Mismatch 錯誤的根本。

水合的本質:伺服器回傳的 HTML 是靜態的,沒有事件監聽器和響應式狀態。Vue 的水合過程就是在不重新渲染 DOM 的前提下,將響應式狀態和事件處理器「接管」到已有的 DOM 元素上。這個過程需要客戶端的虛擬 DOM 結構與伺服器端生成的 HTML 結構完全一致

水合不匹配(Hydration Mismatch)的根源:當伺服器端和客戶端渲染同一個組件時,若輸出不同的 HTML,Vue 會在控制台輸出警告並嘗試修復(即重新渲染),這會導致頁面閃爍和效能損耗。常見的觸發場景包括:

  • setup() 中直接讀取 windowdocument 等瀏覽器 API(伺服器端這些 API 不存在)
  • 依賴當前時間(new Date())生成內容(伺服器時間與客戶端渲染時間不同)
  • 使用隨機數生成 ID
  • 依賴 Cookie 或 localStorage 決定初始渲染

解決方案的核心是確保初始渲染輸出的一致性。對於真正需要在客戶端才能確定的內容,使用 Nuxt 的 <ClientOnly> 組件包裹,告訴 Vue 這部分內容不參與水合對比,僅在客戶端渲染。

4. 頁面組件生命週期

在理解雙環境執行模型後,Vue 3 的生命週期鉤子在 Nuxt 中的使用規則變得清晰:

setup() / <script setup> 頂層代碼:在伺服器端和客戶端都會執行。適合放置響應式狀態定義和資料獲取(使用 useFetch/useAsyncData)。

onMounted()僅在客戶端執行。原因是掛載(mounting)意味著將虛擬 DOM 插入真實 DOM,而伺服器端沒有 DOM。因此所有依賴 DOM 操作、瀏覽器 API(window.scrollYIntersectionObserverlocalStorage 等)的邏輯都應放在 onMounted() 中。

onBeforeUnmount() / onUnmounted():清理工作的標準位置。WebSocket 連線、定時器、事件監聽器等資源都應在此處釋放,避免記憶體洩漏。特別在 Nuxt 的 SPA 路由模式下,頁面跳轉時舊組件會卸載,若不清理計時器,背景任務會在用戶已離開頁面後繼續執行。

watch()watchEffect():兩者都在伺服器端執行一次(初始化),後續只在客戶端響應式觸發。如果你的 watch 回調依賴瀏覽器 API,需加上 { immediate: false } 或將邏輯移入 onMounted


SEO 最佳實踐

理解搜索引擎如何爬取 Nuxt 應用

搜尋引擎爬蟲(Googlebot 等)有兩種工作模式:靜態爬取JavaScript 渲染爬取。靜態爬取直接解析 HTML 是最快速且可靠的;JavaScript 渲染爬取雖然 Googlebot 已支援,但通常有數天到數週的延遲,且資源消耗龐大。Nuxt SSR 的核心 SEO 優勢就在這裡:每個頁面請求都能返回包含完整內容的 HTML,爬蟲無需執行 JavaScript 即可索引所有文字內容、標題和結構化資料。

元數據管理的層次結構

Nuxt 3 提供了三個層次的 SEO 配置,優先級從高到低:

組件層(最高優先級):在頁面組件中使用 useSeoMeta() 設定當前頁面的 meta 標籤。useSeoMeta() 是 Nuxt 3 推薦的 SEO API,它能識別超過 100 種 meta 屬性,並自動處理重複標籤問題(例如同時設定 og:titletwitter:title 時不會產生衝突)。

1
2
3
4
5
6
7
8
9
10
11
<script setup>
const route = useRoute()
const { data: post } = await useFetch(`/api/posts/${route.params.slug}`)

useSeoMeta({
title: () => post.value?.title,
description: () => post.value?.excerpt,
ogImage: () => post.value?.coverImage,
twitterCard: 'summary_large_image',
})
</script>

佈局層(中等優先級):在 layouts/ 中設定常見的 meta 標籤,如網站名稱、作者資訊等,適用於整個佈局下的所有頁面。

全局層(最低優先級):在 nuxt.config.tsapp.head 中設定基礎 meta 標籤,作為所有頁面的默認值。頁面層的設定會覆蓋全局層,形成清晰的繼承關係。

Open Graph 與 Twitter Card

Open Graph(OG)是 Facebook 推動的一個開放標準,現已被 LinkedIn、Discord、Slack 等幾乎所有社交媒體和通訊工具支援。當用戶分享你的頁面連結時,這些平台會讀取頁面的 OG 標籤來生成預覽卡片——包括標題、描述和縮圖。關鍵的 OG 屬性包括:

  • og:title:分享卡片的標題,可以與頁面 <title> 不同(例如去掉網站名稱後綴,只保留文章標題)
  • og:description:分享卡片的描述,建議 150-200 字,應能讓讀者不看全文就想點擊
  • og:image:分享圖片,建議尺寸 1200×630 像素,超過 600KB 的圖片可能被部分平台忽略
  • og:type:內容類型,常用值有 website(首頁)、article(文章)

useSeoMeta()twitterCard: 'summary_large_image' 等屬性已自動同步設定 Twitter Card,開發者不需要單獨維護兩套標籤。

JSON-LD 結構化數據

結構化數據是告訴搜尋引擎「這個頁面是什麼類型的內容」的標準方式。Google 利用 JSON-LD 格式的結構化數據生成豐富搜尋結果(Rich Results):文章卡片、食譜步驟、FAQ 折疊、事件資訊、產品評分等。

JSON-LD 的優勢是以 <script type="application/ld+json"> 標籤嵌入 HTML,與可視化內容完全解耦——你不需要在頁面上顯示這些數據,但搜尋引擎能夠讀取它。對於部落格文章,添加 BlogPosting 類型的結構化數據可以讓文章在搜尋結果中獲得更豐富的展示,包括作者資訊、發布日期和圖片預覽。


常見問題深入分析

水合不匹配的診斷與修復

根本診斷方法:在瀏覽器中禁用 JavaScript,查看頁面初始 HTML,再與啟用 JavaScript 後的頁面進行對比。若兩者結構不同,那就是不匹配的根源。

修復策略的核心原則是讓初始渲染保持確定性:任何在初始 setup() 中可能因執行環境(伺服器/客戶端)或時間差異而產生不同輸出的邏輯,都應當延遲到 onMounted() 中執行,或使用 <ClientOnly> 包裹。常見修復模式:

  • 讀取瀏覽器寬度決定顯示:移入 onMounted(),初始值設為 null,用 v-if 控制顯示
  • 顯示當前用戶名:確保伺服器端也能通過 Cookie/session 獲取用戶資訊,而非只在客戶端讀取 localStorage
  • 隨機生成動畫初始偏移量:移入 onMounted(),或使用固定種子的偽隨機函數

動態路由的 SEO 挑戰

對於有大量動態路由的網站(如文章列表頁、商品頁),SEO 策略的選擇直接影響索引效果:

SSR 模式:每次請求都在伺服器端動態生成帶有最新資料的 HTML。優點是內容始終最新,適合頻繁更新的內容(如新聞、電商商品)。缺點是對伺服器資源消耗較大,且響應時間受資料庫查詢速度影響。

SSG + ISR(增量靜態再生):在建置時預渲染常見頁面,配合 Nuxt 的 routeRules 設定再驗證時間(stale-while-revalidate)。過期的頁面在下次請求時會在背景重新生成,同時繼續提供舊版本。這種策略在 Vercel 等邊緣運算平台上能達到極致效能,適合內容更新頻率中等的場景。

Sitemap 的重要性:動態路由的頁面因為 URL 路徑帶有變數,爬蟲不一定能自動發現所有頁面。提交包含所有動態路由 URL 的 Sitemap 給 Google Search Console,能顯著提升索引覆蓋率。@nuxtjs/sitemap 模組可以根據應用路由和 API 數據自動生成完整的 Sitemap。

第三方腳本的效能影響

Google Analytics、Facebook Pixel、客服聊天工具等第三方腳本是 Core Web Vitals 分數的主要殺手,尤其是 LCP(最大內容繪製)TBT(總阻塞時間)

優化策略的核心是控制腳本加載時機:使用 defer 屬性讓腳本在 HTML 解析完成後才執行;對於非關鍵腳本,延遲到頁面首次互動後才開始加載(lazyOnload 策略);將統計腳本的初始化放在 .client.ts 插件中,確保它只在客戶端執行且不阻塞 SSR。


效能測試與 SEO 驗證

Core Web Vitals 的實際意義

Google 的 Core Web Vitals 是衡量用戶體驗的三個核心指標,自 2021 年起作為搜尋排名因素:

LCP(Largest Contentful Paint,最大內容繪製):頁面最大的可視元素(通常是主圖或標題)完成渲染的時間,目標 ≤ 2.5 秒。主要優化方向:圖片優化(WebP 格式、正確尺寸設定)、伺服器響應速度(TTFB)、關鍵 CSS 內聯避免渲染阻塞。

CLS(Cumulative Layout Shift,累積佈局偏移):頁面加載過程中視覺元素意外移動的幅度,目標 ≤ 0.1。常見原因:圖片未設置寬高導致佈局抖動、廣告或嵌入元素延遲加載推擠內容、字體加載前後字形寬度不同(FOUT)。

INP(Interaction to Next Paint,互動到下次繪製):用戶操作後頁面響應速度,目標 ≤ 200ms(2024 年已取代原來的 FID)。主要優化方向:減少長任務(Long Tasks,超過 50ms 的 JavaScript 執行)、避免在事件處理器中執行繁重計算、使用 requestIdleCallback 進行非關鍵工作。

SEO 審核工具鏈

在正式上線前,建議完成以下驗證步驟:

  1. Google Search Console 的 URL 檢查工具:直接讓 Googlebot 爬取並渲染你的頁面,確認實際抓取結果,這是最接近真實 SEO 表現的測試
  2. Rich Results Test(豐富搜尋結果測試):驗證 JSON-LD 結構化數據是否符合格式要求,預覽在搜尋結果中的展示效果
  3. Lighthouse(Chrome DevTools 內建):一鍵生成 Performance、SEO、Accessibility 綜合評分,並提供具體優化建議
  4. Open Graph Debugger(Facebook、LinkedIn 等各平台提供):預覽分享卡片外觀,確保圖片尺寸正確且描述符合要求

總結

Nuxt 的 SSR 架構是一個精心設計的系統,每個設計決策都圍繞著「如何讓伺服器端和客戶端協作,同時保持開發者體驗」這個核心目標。

關鍵心智模型

  1. 每個請求都走兩次:服務端生成 HTML,客戶端接管互動。資料獲取邏輯要為「payload hydration」設計,利用 useFetch/useAsyncData 避免重複請求
  2. 插件後綴決定環境邊界.server.ts / .client.ts 後綴是隔離環境相依性的正確方式,優先於在代碼中用 if (process.server) 判斷
  3. useSeoMeta 優於直接操作 head:它處理了標籤去重、響應式更新等複雜情況,且支援函數式語法自動追蹤依賴
  4. Core Web Vitals 是排名訊號:LCP、CLS、INP 的優化應貫穿整個開發流程,而非上線前的補救措施
  5. 結構化數據是長期投資:雖然效果難以立即量化,但 JSON-LD 是獲得 Google 豐富搜尋展示的必要條件

透過掌握這些核心概念,你將能夠在 Nuxt 開發中做出更準確的架構決策,建構既有優秀 SEO 表現又有流暢用戶體驗的現代 Web 應用。