在伺服端渲染的世界,沒有 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 來說不友善!
Server-Side Rendering (SSR, 伺服器端渲染)
SSR (Server Side Render) 是一種將伺服器端渲染和客戶端渲染結合起來的技術,它可以在伺服器端產生 HTML 程式碼,並將其傳送到瀏覽器端。
那麼問題來了:如果我們把「沒有 Virtual DOM」的 Vapor 編譯結果放進 SSR 的世界,Hydration 還能順利進行嗎?前端的 Vue-Router 切頁時的 effectScope
又會不會跟 SSR 的資料預取打架?
Vapor Mode 的核心是將 .vue 模板直接編譯成 DOM Patch 指令,而不是 Virtual DOM 物件,然而 SSR 的流程是:
Server 端渲染 (renderToString):產出一段 HTML。
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。
SSR 下啟用 Vapor,需要特別注意以下幾點:
避免直接操作 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>
善用 Partial Hydration
所謂 Partial Hydration 指的是「僅對互動區域進行客戶端 Hydration」。
例如一個新聞頁面,文章正文可完全由 SSR 輸出,而留言區則透過 <client-only>
或 v-if="isClient"
配合 Vapor 進行快速互動。
<template>
<ArticleContent :html="serverHTML" />
<client-only>
<CommentBox />
</client-only>
</template>
effectScope
的管理Vapor Mode 強調 effectScope
來掌控響應範圍,而前端路由切換往往會頻繁建立 / 銷毀頁面組件。
以下兩個場景特別要注意:
場景一、keep-alive
:
如果路由頁面被 keep-alive
緩存,每次切換時記得不要重複建立 effectScope
。
建議在 onActivated
/ onDeactivated
事件中控制 scope 啟動與清理。
場景二、Route-level Scope:
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()
})
keep-alive + Vapor:在 keep-alive 裡存很多 effectScope
,切換時要確保 scope 行為符合預期(不要重複建立)。
Route 應用:建議把資料載入(fetch)與 effectScope
綁到 route-level(在 setup 或 route guard 中適度啟動 / 停止)。
用 Nuxt3 搭配建立一個專案
npx nuxi init nuxt-vapor-demo
cd nuxt-vapor-demo
npm install
npm add vue@3.6.0
nuxt.config.ts
export default defineNuxtConfig({
experimental: {
componentIslands: true, // 保留 partial hydration 能力
},
vite: {
vue: {
vapor: true, // 開啟 Vapor Mode
},
},
})
// 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>
npm run dev
進入 首頁 (index.vue)
點擊「點擊 +1」按鈕。
在 Devtools → Components 面板中,觀察 count
的變化。
Vapor Mode 下:變動只更新 DOM textNode,不會有多餘的 component re-render。
切換到 About 頁面
看 data 是否由 SSR 預取(已經帶有值)。
在 Components 面板 → props/data 區域中,驗證資料與 UI 同步。
傳統 VDOM Hydration 會有「component 樹重建 → 再 patch」的過程,而 Vapor Hydration 幾乎是 0-cost,因為它直接對 DOM 做最小修補。
在 Devtools → Performance 面板點選「⚡️ Start Profiling」。
在首頁 連點 100 次按鈕來模擬大量更新。
停止 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 帶來的極速互動。