iT邦幫忙

2025 iThome 鐵人賽

DAY 28
0
Vue.js

Vue3.6 的革新:深入理解 Composition API系列 第 28

Day 28: SSR / Router - 全鏈路 Vapor 化挑戰

  • 分享至 

  • xImage
  •  

在伺服端渲染的世界,沒有 Virtual DOM... Vue 如何站穩腳步?

在前幾天的內容中,我們已經看過 Vapor Mode 如何跳過 Virtual DOM Diff、以及 Alien Signals 如何在多個 composable 間維持高效依賴追蹤。

但現代前端開發不只停留在 Client-Side Rendering (CSR),大型應用往往同時仰賴 Server-Side Rendering (SSR)前端路由 (Router) 才能達到最佳的 SEO、首屏速度與互動體驗。

Client-Side Rendering (CSR, 客戶端渲染):SPA (Single Page Application) 單一頁面應用程式,一般也稱為客戶端渲染 CSR (Client Side Render)。

瀏覽器向伺服器發出請求,會回傳 js 以及空的 HTML 給畫面,所以會看到白白的一片!
前端透過 AJAX 發送請求取得 JSON 回傳格式渲染在畫面上,但是爬蟲當下只會爬到空白的頁面,裡面都沒有資料,所以 SPA 對 SEO 來說不友善!

CSR

Server-Side Rendering (SSR, 伺服器端渲染)

SSR (Server Side Render) 是一種將伺服器端渲染和客戶端渲染結合起來的技術,它可以在伺服器端產生 HTML 程式碼,並將其傳送到瀏覽器端。

SSR

那麼問題來了:如果我們把「沒有 Virtual DOM」的 Vapor 編譯結果放進 SSR 的世界,Hydration 還能順利進行嗎?前端的 Vue-Router 切頁時的 effectScope 又會不會跟 SSR 的資料預取打架?

SSR 對 Vapor 是挑戰?


Vapor Mode 的核心是將 .vue 模板直接編譯成 DOM Patch 指令,而不是 Virtual DOM 物件,然而 SSR 的流程是:

  1. Server 端渲染 (renderToString):產出一段 HTML。

  2. Client 端 Hydration:將瀏覽器中的靜態 HTML 與「程式狀態」對齊。

傳統 Virtual DOM 會透過 VNode 結構 進行比對,確保 HTML 與狀態一致;但 Vapor 沒有 VNode,需要直接比對 DOM 節點與 Patch 指令。

所以 SSR 輸出的 HTML 與 Patch 指令如果不同步,就可能出現 Hydration mismatch

現階段的官方策略


Vue 3.6 對此採取 保守策略

Server:輸出完整 HTML,維持與傳統 SSR 一樣的渲染流程。

Client:啟動 Vapor Patch 指令時,仍做一次 最小化的 Hydration 探測,確認 DOM 與狀態同步。

進階:在非關鍵互動區域保留傳統 Hydration,僅對「熱區」漸進啟用 Vapor。

換句話說,不用一次性全站 Vapor 化,可以「局部切換」;例如:首頁 SEO 區塊用 SSR + 傳統 Hydration,互動表單改用 Vapor Patch。

Hydration 細節


SSR 下啟用 Vapor,需要特別注意以下幾點:

  1. 避免直接操作 DOM
    不要在 SSR 階段執行 window、document 等僅存在於瀏覽器的 API;若需要資料預取,應使用 onServerPrefetch

    <script setup>
    import { onServerPrefetch } from 'vue'
    import { useFetch } from '@/composables/useFetch'
    
    const { data, load } = useFetch('/api/data')
    
    // 在 server 階段先取得資料
    onServerPrefetch(load)
    </script>
    
    <template>
      <div>{{ data }}</div>
    </template>
    
  2. 善用 Partial Hydration
    所謂 Partial Hydration 指的是「僅對互動區域進行客戶端 Hydration」。

    例如一個新聞頁面,文章正文可完全由 SSR 輸出,而留言區則透過 <client-only>v-if="isClient" 配合 Vapor 進行快速互動。

    <template>
      <ArticleContent :html="serverHTML" />
      <client-only>
        <CommentBox />
      </client-only>
    </template>
    

Router 與 effectScope 的管理


Vapor Mode 強調 effectScope 來掌控響應範圍,而前端路由切換往往會頻繁建立 / 銷毀頁面組件。
以下兩個場景特別要注意:

場景一、keep-alive

  • 如果路由頁面被 keep-alive 緩存,每次切換時記得不要重複建立 effectScope

  • 建議在 onActivated / onDeactivated 事件中控制 scope 啟動與清理。

場景二、Route-level Scope

  • 建議將資料載入 (fetch) 與 effectScope 綁到 route guard 或 setup,確保切頁時自動清理。

範例

import { effectScope } from 'vue'
import { useAutoRefresh } from '@/composables/useAutoRefresh'

let pageScope: ReturnType<typeof effectScope> | null = null

router.beforeEach((to, from, next) => {
  pageScope?.stop()
  pageScope = effectScope()
  pageScope.run(() => {
    useAutoRefresh(() => fetch(`/api/${to.name}`))
  })
  next()
})

Router 實作要點


keep-alive + Vapor:在 keep-alive 裡存很多 effectScope,切換時要確保 scope 行為符合預期(不要重複建立)。

Route 應用:建議把資料載入(fetch)與 effectScope 綁到 route-level(在 setup 或 route guard 中適度啟動 / 停止)。

範例


用 Nuxt3 搭配建立一個專案

step0: 事前準備環境

建立 Nuxt3 專案先決條件

step1: 建立專案

npx nuxi init nuxt-vapor-demo
cd nuxt-vapor-demo
npm install

step2: 安裝 Vue 3.6+ (支援 Vapor)

npm add vue@3.6.0

step3: 編輯 nuxt.config.ts

export default defineNuxtConfig({
  experimental: {
    componentIslands: true,  // 保留 partial hydration 能力
  },
  vite: {
    vue: {
      vapor: true,  // 開啟 Vapor Mode
    },
  },
})

step4: 建立頁面

// pages/index.vue
<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)
const add = () => count.value++
</script>

<template>
  <div>
    <h1>Nuxt 3 + Vapor SSR Demo</h1>
    <button @click="add">點擊 +1</button>
    <p>目前數字:{{ count }}</p>
  </div>
</template>
// pages/about.vue
<script setup lang="ts">
import { ref, onServerPrefetch } from 'vue'

const data = ref<string | null>(null)

async function load() {
  data.value = `Server Data @ ${new Date().toISOString()}`
}

// SSR 階段先抓資料
onServerPrefetch(load)
</script>

<template>
  <div>
    <h1>About Page</h1>
    <p>{{ data }}</p>
  </div>
</template>

step5: 啟動專案

npm run dev

step6: Chrome 擴充 Vue Devtools 功能

Components 面板比較

  1. 進入 首頁 (index.vue)

    • 點擊「點擊 +1」按鈕。

    • 在 Devtools → Components 面板中,觀察 count 的變化。

    • Vapor Mode 下:變動只更新 DOM textNode,不會有多餘的 component re-render。

  2. 切換到 About 頁面

    • 看 data 是否由 SSR 預取(已經帶有值)。

    • 在 Components 面板 → props/data 區域中,驗證資料與 UI 同步。

傳統 VDOM Hydration 會有「component 樹重建 → 再 patch」的過程,而 Vapor Hydration 幾乎是 0-cost,因為它直接對 DOM 做最小修補。

Performance 面板比較

  1. 在 Devtools → Performance 面板點選「⚡️ Start Profiling」。

  2. 在首頁 連點 100 次按鈕來模擬大量更新

  3. 停止 Profiling → 查看 Flamegraph。

在 Vapor Mode 下:Update cost 幾乎只落在 text node patch;沒有 VNode diff / re-render trace。
在傳統模式 (關閉 vapor: true) 下:看到「component update」與「VNode patch」堆疊較多。

小結


Vapor Mode 在 CSR 世界中展現了驚人的效能,但是當我們進入 SSR + Router 的場景,就必須重新思考 Hydration、資料預取與 scope 管理的策略。

好消息是,Vue 3.6 的設計讓我們可以 漸進式導入 既能保有 SEO 與第一次載入速度,也能享受 Vapor 帶來的極速互動。

參考資料


  1. 理解 Hydration 的 SSR:優點、缺點以及軟體架構師的場景
  2. 為什麼要選擇 SSR
  3. Vue.sj - ssr hydration-mismatch
  4. Vue 渲染模式的演變:邁向 Vapor 模式的新時代
  5. What are Server Side Rendering (SSR) &amp; Client Side Rendering (CSR) | Pros + Cons

上一篇
Day 27: 狀態管理的下一步 - Pinia × Alien Signals
系列文
Vue3.6 的革新:深入理解 Composition API28
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言