做自(zì)由與創造的先行者

服務端渲染 (SSR)

Vue.js中文(wén)手冊

總覽 ​

什(shén)麽是 SSR? ​

Vue.js 是一個用(yòng)于構建客戶端應用(yòng)的框架。默認情況下(xià),Vue 組件的職責是在浏覽器中生成和(hé)操作(zuò) DOM。然而,Vue 也(yě)支持将組件在服務端直接渲染成 HTML 字符串,作(zuò)爲服務端響應返回給浏覽器,最後在浏覽器端将靜态的 HTML“激活”(hydrate) 爲能(néng)夠交互的客戶端應用(yòng)。

一個由服務端渲染的 Vue.js 應用(yòng)也(yě)可以被認爲是“同構的”(Isomorphic) 或“通用(yòng)的”(Universal),因爲應用(yòng)的大(dà)部分代碼同時(shí)運行在服務端和(hé)客戶端。

爲什(shén)麽要用(yòng) SSR? ​

與客戶端的單頁應用(yòng) (SPA) 相比,SSR 的優勢主要在于:

更快(kuài)的首屏加載:這(zhè)一點在慢網速或者運行緩慢的設備上(shàng)尤爲重要。服務端渲染的 HTML 無需等到(dào)所有的 JavaScript 都下(xià)載并執行完成之後才顯示,所以你(nǐ)的用(yòng)戶将會(huì)更快(kuài)地看(kàn)到(dào)完整渲染的頁面。除此之外(wài),數據獲取過程在首次訪問時(shí)在服務端完成,相比于從(cóng)客戶端獲取,可能(néng)有更快(kuài)的數據庫連接。這(zhè)通常可以帶來(lái)更高(gāo)的核心 Web 指标評分、更好(hǎo)的用(yòng)戶體驗,而對(duì)于那些(xiē)“首屏加載速度與轉化率直接相關”的應用(yòng)來(lái)說,這(zhè)點可能(néng)至關重要。

統一的心智模型:你(nǐ)可以使用(yòng)相同的語言以及相同的聲明(míng)式、面向組件的心智模型來(lái)開(kāi)發整個應用(yòng),而不需要在後端模闆系統和(hé)前端框架之間來(lái)回切換。

更好(hǎo)的 SEO:搜索引擎爬蟲可以直接看(kàn)到(dào)完全渲染的頁面。

TIP

截至目前,Google 和(hé) Bing 可以很(hěn)好(hǎo)地對(duì)同步 JavaScript 應用(yòng)進行索引。這(zhè)裏的“同步”是關鍵詞。如果你(nǐ)的應用(yòng)以一個 loading 動畫(huà)開(kāi)始,然後通過 Ajax 獲取内容,爬蟲并不會(huì)等到(dào)内容加載完成再抓取。也(yě)就是說,如果 SEO 對(duì)你(nǐ)的頁面至關重要,而你(nǐ)的内容又是異步獲取的,那麽 SSR 可能(néng)是必需的。

使用(yòng) SSR 時(shí)還有一些(xiē)權衡之處需要考量:

開(kāi)發中的限制。浏覽器端特定的代碼隻能(néng)在某些(xiē)生命周期鈎子中使用(yòng);一些(xiē)外(wài)部庫可能(néng)需要特殊處理(lǐ)才能(néng)在服務端渲染的應用(yòng)中運行。

更多的與構建配置和(hé)部署相關的要求。服務端渲染的應用(yòng)需要一個能(néng)讓 Node.js 服務器運行的環境,不像完全靜态的 SPA 那樣可以部署在任意的靜态文(wén)件服務器上(shàng)。

更高(gāo)的服務端負載。在 Node.js 中渲染一個完整的應用(yòng)要比僅僅托管靜态文(wén)件更加占用(yòng) CPU 資源,因此如果你(nǐ)預期有高(gāo)流量,請(qǐng)爲相應的服務器負載做好(hǎo)準備,并采用(yòng)合理(lǐ)的緩存策略。

在爲你(nǐ)的應用(yòng)使用(yòng) SSR 之前,你(nǐ)首先應該問自(zì)己是否真的需要它。這(zhè)主要取決于首屏加載速度對(duì)應用(yòng)的重要程度。例如,如果你(nǐ)正在開(kāi)發一個内部的管理(lǐ)面闆,初始加載時(shí)的那額外(wài)幾百毫秒對(duì)你(nǐ)來(lái)說并不重要,這(zhè)種情況下(xià)使用(yòng) SSR 就沒有太多必要了(le)。然而,在内容展示速度極其重要的場景下(xià),SSR 可以盡可能(néng)地幫你(nǐ)實現(xiàn)最優的初始加載性能(néng)。

SSR vs. SSG ​

靜态站(zhàn)點生成 (Static-Site Generation,縮寫爲 SSG),也(yě)被稱爲預渲染,是另一種流行的構建快(kuài)速網站(zhàn)的技術。如果用(yòng)服務端渲染一個頁面所需的數據對(duì)每個用(yòng)戶來(lái)說都是相同的,那麽我們可以隻渲染一次,提前在構建過程中完成,而不是每次請(qǐng)求進來(lái)都重新渲染頁面。預渲染的頁面生成後作(zuò)爲靜态 HTML 文(wén)件被服務器托管。

SSG 保留了(le)和(hé) SSR 應用(yòng)相同的性能(néng)表現(xiàn):它帶來(lái)了(le)優秀的首屏加載性能(néng)。同時(shí),它比 SSR 應用(yòng)的花(huā)銷更小(xiǎo),也(yě)更容易部署,因爲它輸出的是靜态 HTML 和(hé)資源文(wén)件。這(zhè)裏的關鍵詞是靜态:SSG 僅可以用(yòng)于消費靜态數據的頁面,即數據在構建期間就是已知(zhī)的,并且在多次部署期間不會(huì)改變。每當數據變化時(shí),都需要重新部署。

如果你(nǐ)調研 SSR 隻是爲了(le)優化爲數不多的營銷頁面的 SEO (例如 /、/about 和(hé) /contact 等),那麽你(nǐ)可能(néng)需要 SSG 而不是 SSR。SSG 也(yě)非常适合構建基于内容的網站(zhàn),比如文(wén)檔站(zhàn)點或者博客。事(shì)實上(shàng),你(nǐ)現(xiàn)在正在閱讀的這(zhè)個網站(zhàn)就是使用(yòng) VitePress 靜态生成的,它是一個由 Vue 驅動的靜态站(zhàn)點生成器。

基礎教程 ​

渲染一個應用(yòng) ​

讓我們來(lái)看(kàn)一個 Vue SSR 最基礎的實戰示例。

創建一個新的文(wén)件夾,cd 進入

執行 npm init -y

在 package.json 中添加 "type": "module" 使 Node.js 以 ES modules mode 運行

執行 npm install vue

創建一個 example.js 文(wén)件:

js

// 此文(wén)件運行在 Node.js 服務器上(shàng)

import { createSSRApp } from 'vue'

// Vue 的服務端渲染 API 位于 `vue/server-renderer` 路徑下(xià)

import { renderToString } from 'vue/server-renderer'

const app = createSSRApp({

data: () => ({ count: 1 }),

template: `<button @click="count++">{{ count }}</button>`

})

renderToString(app).then((html) => {

console.log(html)

})

接着運行:

sh

> node example.js

它應該會(huì)在命令行中打印出如下(xià)内容:

<button>1</button>

renderToString() 接收一個 Vue 應用(yòng)實例作(zuò)爲參數,返回一個 Promise,當 Promise resolve 時(shí)得到(dào)應用(yòng)渲染的 HTML。當然你(nǐ)也(yě)可以使用(yòng) Node.js Stream API 或者 Web Streams API 來(lái)執行流式渲染。查看(kàn) SSR API 參考獲取完整的相關細節。

然後我們可以把 Vue SSR 的代碼移動到(dào)一個服務器請(qǐng)求處理(lǐ)函數裏,它将應用(yòng)的 HTML 片段包裝爲完整的頁面 HTML。接下(xià)來(lái)的幾步我們将會(huì)使用(yòng) express:

執行 npm install express

創建下(xià)面的 server.js 文(wén)件:

js

import express from 'express'

import { createSSRApp } from 'vue'

import { renderToString } from 'vue/server-renderer'

const server = express()

server.get('/', (req, res) => {

const app = createSSRApp({

data: () => ({ count: 1 }),

template: `<button @click="count++">{{ count }}</button>`

})

renderToString(app).then((html) => {

res.send(`

<!DOCTYPE html>

<html>

<head>

<title>Vue SSR Example</title>

</head>

<body>

<div id="app">${html}</div>

</body>

</html>

`)

})

})

server.listen(3000, () => {

console.log('ready')

})

最後,執行 node server.js,訪問 http://localhost:3000。你(nǐ)應該可以看(kàn)到(dào)頁面中的按鈕了(le)。

在 StackBlitz 上(shàng)試試

客戶端激活 ​

如果你(nǐ)點擊該按鈕,你(nǐ)會(huì)發現(xiàn)數字并沒有改變。這(zhè)段 HTML 在客戶端是完全靜态的,因爲我們沒有在浏覽器中加載 Vue。

爲了(le)使客戶端的應用(yòng)可交互,Vue 需要執行一個激活步驟。在激活過程中,Vue 會(huì)創建一個與服務端完全相同的應用(yòng)實例,然後将每個組件與它應該控制的 DOM 節點相匹配,并添加 DOM 事(shì)件監聽器。

爲了(le)在激活模式下(xià)挂載應用(yòng),我們應該使用(yòng) createSSRApp() 而不是 createApp():

js

// 該文(wén)件運行在浏覽器中

import { createSSRApp } from 'vue'

const app = createSSRApp({

// ...和(hé)服務端完全一緻的應用(yòng)實例

})

// 在客戶端挂載一個 SSR 應用(yòng)時(shí)會(huì)假定

// HTML 是預渲染的,然後執行激活過程,

// 而不是挂載新的 DOM 節點

app.mount('#app')

代碼結構 ​

想想我們該如何在客戶端複用(yòng)服務端的應用(yòng)實現(xiàn)。這(zhè)時(shí)我們就需要開(kāi)始考慮 SSR 應用(yòng)中的代碼結構了(le)——我們如何在服務器和(hé)客戶端之間共享相同的應用(yòng)代碼呢(ne)?

這(zhè)裏我們将演示最基礎的設置。首先,讓我們将應用(yòng)的創建邏輯拆分到(dào)一個單獨的文(wén)件 app.js 中:

js

// app.js (在服務器和(hé)客戶端之間共享)

import { createSSRApp } from 'vue'

export function createApp() {

return createSSRApp({

data: () => ({ count: 1 }),

template: `<button @click="count++">{{ count }}</button>`

})

}

該文(wén)件及其依賴項在服務器和(hé)客戶端之間共享——我們稱它們爲通用(yòng)代碼。編寫通用(yòng)代碼時(shí)有一些(xiē)注意事(shì)項,我們将在下(xià)面讨論。

我們在客戶端入口導入通用(yòng)代碼,創建應用(yòng)并執行挂載:

js

// client.js

import { createApp } from './app.js'

createApp().mount('#app')

服務器在請(qǐng)求處理(lǐ)函數中使用(yòng)相同的應用(yòng)創建邏輯:

js

// server.js (不相關的代碼省略)

import { createApp } from './app.js'

server.get('/', (req, res) => {

const app = createApp()

renderToString(app).then(html => {

// ...

})

})

此外(wài),爲了(le)在浏覽器中加載客戶端文(wén)件,我們還需要:

在 server.js 中添加 server.use(express.static('.')) 來(lái)托管客戶端文(wén)件。

将 <script type="module" src="/client.js"></script> 添加到(dào) HTML 外(wài)殼以加載客戶端入口文(wén)件。

通過在 HTML 外(wài)殼中添加 Import Map 以支持在浏覽器中使用(yòng) import * from 'vue'。

在 StackBlitz 上(shàng)嘗試完整的示例。按鈕現(xiàn)在可以交互了(le)!

更通用(yòng)的解決方案 ​

從(cóng)上(shàng)面的例子到(dào)一個生産就緒的 SSR 應用(yòng)還需要很(hěn)多工(gōng)作(zuò)。我們将需要:

支持 Vue SFC 且滿足其他(tā)構建步驟要求。事(shì)實上(shàng),我們需要爲同一個應用(yòng)執行兩次構建過程:一次用(yòng)于客戶端,一次用(yòng)于服務器。

TIP

Vue 組件用(yòng)在 SSR 時(shí)的編譯産物不同——模闆被編譯爲字符串拼接而不是 render 函數,以此提高(gāo)渲染性能(néng)。

在服務器請(qǐng)求處理(lǐ)函數中,确保返回的 HTML 包含正确的客戶端資源鏈接和(hé)最優的資源加載提示 (如 prefetch 和(hé) preload)。我們可能(néng)還需要在 SSR 和(hé) SSG 模式之間切換,甚至在同一個應用(yòng)中混合使用(yòng)這(zhè)兩種模式。

以一種通用(yòng)的方式管理(lǐ)路由、數據獲取和(hé)狀态存儲。

完整的實現(xiàn)會(huì)非常複雜(zá),并且取決于你(nǐ)選擇使用(yòng)的構建工(gōng)具鏈。因此,我們強烈建議(yì)你(nǐ)使用(yòng)一種更通用(yòng)的、更集成化的解決方案,幫你(nǐ)抽象掉那些(xiē)複雜(zá)的東西。下(xià)面推薦幾個 Vue 生态中的 SSR 解決方案。

Nuxt ​

Nuxt 是一個構建于 Vue 生态系統之上(shàng)的全棧框架,它爲編寫 Vue SSR 應用(yòng)提供了(le)絲滑的開(kāi)發體驗。更棒的是,你(nǐ)還可以把它當作(zuò)一個靜态站(zhàn)點生成器來(lái)用(yòng)!我們強烈建議(yì)你(nǐ)試一試。

Quasar ​

Quasar 是一個基于 Vue 的完整解決方案,它可以讓你(nǐ)用(yòng)同一套代碼庫構建不同目标的應用(yòng),如 SPA、SSR、PWA、移動端應用(yòng)、桌面端應用(yòng)以及浏覽器插件。除此之外(wài),它還提供了(le)一整套 Material Design 風(fēng)格的組件庫。

Vite SSR ​

Vite 提供了(le)内置的 Vue 服務端渲染支持,但(dàn)它在設計(jì)上(shàng)是偏底層的。如果你(nǐ)想要直接使用(yòng) Vite,可以看(kàn)看(kàn) vite-plugin-ssr,一個幫你(nǐ)抽象掉許多複雜(zá)細節的社區(qū)插件。

你(nǐ)也(yě)可以在這(zhè)裏查看(kàn)一個使用(yòng)手動配置的 Vue + Vite SSR 的示例項目,以它作(zuò)爲基礎來(lái)構建。請(qǐng)注意,這(zhè)種方式隻有在你(nǐ)有豐富的 SSR 和(hé)構建工(gōng)具經驗,并希望對(duì)應用(yòng)的架構做深入的定制時(shí)才推薦使用(yòng)。

書寫 SSR 友好(hǎo)的代碼 ​

無論你(nǐ)的構建配置或頂層框架的選擇如何,下(xià)面的原則在所有 Vue SSR 應用(yòng)中都适用(yòng)。

服務端的響應性 ​

在 SSR 期間,每一個請(qǐng)求 URL 都會(huì)映射到(dào)我們應用(yòng)中的一個期望狀态。因爲沒有用(yòng)戶交互和(hé) DOM 更新,所以響應性在服務端是不必要的。爲了(le)更好(hǎo)的性能(néng),默認情況下(xià)響應性在 SSR 期間是禁用(yòng)的。

組件生命周期鈎子 ​

因爲沒有任何動态更新,所以像 mounted 或者 updated 這(zhè)樣的生命周期鈎子不會(huì)在 SSR 期間被調用(yòng),而隻會(huì)在客戶端運行。隻有 beforeCreate 和(hé) created 這(zhè)兩個鈎子會(huì)在 SSR 期間被調用(yòng)。

你(nǐ)應該避免在 beforeCreate 和(hé) created中使用(yòng)會(huì)産生副作(zuò)用(yòng)且需要被清理(lǐ)的代碼。這(zhè)類副作(zuò)用(yòng)的常見例子是使用(yòng) setInterval 設置定時(shí)器。我們可能(néng)會(huì)在客戶端特有的代碼中設置定時(shí)器,然後在 beforeUnmount 或 unmounted 中清除。然而,由于 unmount 鈎子不會(huì)在 SSR 期間被調用(yòng),所以定時(shí)器會(huì)永遠存在。爲了(le)避免這(zhè)種情況,請(qǐng)将含有副作(zuò)用(yòng)的代碼放(fàng)到(dào) mounted 中。

訪問平台特有 API ​

通用(yòng)代碼不能(néng)訪問平台特有的 API,如果你(nǐ)的代碼直接使用(yòng)了(le)浏覽器特有的全局變量,比如 window 或 document,他(tā)們會(huì)在 Node.js 運行時(shí)報(bào)錯,反過來(lái)也(yě)一樣。

對(duì)于在服務器和(hé)客戶端之間共享,但(dàn)使用(yòng)了(le)不同的平台 API 的任務,建議(yì)将平台特定的實現(xiàn)封裝在一個通用(yòng)的 API 中,或者使用(yòng)能(néng)爲你(nǐ)做這(zhè)件事(shì)的庫。例如你(nǐ)可以使用(yòng) node-fetch 在服務端和(hé)客戶端使用(yòng)相同的 fetch API。

對(duì)于浏覽器特有的 API,通常的方法是在僅客戶端特有的生命周期鈎子中惰性地訪問它們,例如 mounted。

請(qǐng)注意,如果一個第三方庫編寫時(shí)沒有考慮到(dào)通用(yòng)性,那麽要将它集成到(dào)一個 SSR 應用(yòng)中可能(néng)會(huì)很(hěn)棘手。你(nǐ)或許可以通過模拟一些(xiē)全局變量來(lái)讓它工(gōng)作(zuò),但(dàn)這(zhè)隻是一種 hack 手段并且可能(néng)會(huì)影響到(dào)其他(tā)庫的環境檢測代碼。

跨請(qǐng)求狀态污染 ​

在狀态管理(lǐ)一章中,我們介紹了(le)一種使用(yòng)響應式 API 的簡單狀态管理(lǐ)模式。而在 SSR 環境中,這(zhè)種模式需要一些(xiē)額外(wài)的調整。

上(shàng)述模式在一個 JavaScript 模塊的根作(zuò)用(yòng)域中聲明(míng)共享的狀态。這(zhè)是一種單例模式——即在應用(yòng)的整個生命周期中隻有一個響應式對(duì)象的實例。這(zhè)在純客戶端的 Vue 應用(yòng)中是可以的,因爲對(duì)于浏覽器的每一個頁面訪問,應用(yòng)模塊都會(huì)重新初始化。

然而,在 SSR 環境下(xià),應用(yòng)模塊通常隻在服務器啓動時(shí)初始化一次。同一個應用(yòng)模塊會(huì)在多個服務器請(qǐng)求之間被複用(yòng),而我們的單例狀态對(duì)象也(yě)一樣。如果我們用(yòng)單個用(yòng)戶特定的數據對(duì)共享的單例狀态進行修改,那麽這(zhè)個狀态可能(néng)會(huì)意外(wài)地洩露給另一個用(yòng)戶的請(qǐng)求。我們把這(zhè)種情況稱爲跨請(qǐng)求狀态污染。

從(cóng)技術上(shàng)講,我們可以在每個請(qǐng)求上(shàng)重新初始化所有 JavaScript 模塊,就像我們在浏覽器中所做的那樣。但(dàn)是,初始化 JavaScript 模塊的成本可能(néng)很(hěn)高(gāo),因此這(zhè)會(huì)顯著影響服務器性能(néng)。

推薦的解決方案是在每個請(qǐng)求中爲整個應用(yòng)創建一個全新的實例,包括 router 和(hé)全局 store。然後,我們使用(yòng)應用(yòng)層級的 provide 方法來(lái)提供共享狀态,并将其注入到(dào)需要它的組件中,而不是直接在組件中将其導入:

js

// app.js (在服務端和(hé)客戶端間共享)

import { createSSRApp } from 'vue'

import { createStore } from './store.js'

// 每次請(qǐng)求時(shí)調用(yòng)

export function createApp() {

const app = createSSRApp(/* ... */)

// 對(duì)每個請(qǐng)求都創建新的 store 實例

const store = createStore(/* ... */)

// 提供應用(yòng)級别的 store

app.provide('store', store)

// 也(yě)爲激活過程暴露出 store

return { app, store }

}

像 Pinia 這(zhè)樣的狀态管理(lǐ)庫在設計(jì)時(shí)就考慮到(dào)了(le)這(zhè)一點。請(qǐng)參考 Pinia 的 SSR 指南以了(le)解更多細節。

激活不匹配 ​

如果預渲染的 HTML 的 DOM 結構不符合客戶端應用(yòng)的期望,就會(huì)出現(xiàn)激活不匹配。最常見的激活不匹配是以下(xià)幾種原因導緻的:

組件模闆中存在不符合規範的 HTML 結構,渲染後的 HTML 被浏覽器原生的 HTML 解析行爲糾正導緻不匹配。舉例來(lái)說,一個常見的錯誤是 <div> 不能(néng)被放(fàng)在 <p> 中:

html

<p><div>hi</div></p>

如果我們在服務器渲染的 HTML 中出現(xiàn)這(zhè)樣的代碼,當遇到(dào) <div> 時(shí),浏覽器會(huì)結束第一個 <p>,并解析爲以下(xià) DOM 結構:

html

<p></p>

<div>hi</div>

<p></p>

渲染所用(yòng)的數據中包含随機生成的值。由于同一個應用(yòng)會(huì)在服務端和(hé)客戶端執行兩次,每次執行生成的随機數都不能(néng)保證相同。避免随機數不匹配有兩種選擇:

利用(yòng) v-if + onMounted 讓需要用(yòng)到(dào)随機數的模闆隻在客戶端渲染。你(nǐ)所用(yòng)的上(shàng)層框架可能(néng)也(yě)會(huì)提供簡化這(zhè)個用(yòng)例的内置 API,比如 VitePress 的 <ClientOnly> 組件。

使用(yòng)一個能(néng)夠接受随機種子的随機數生成庫,并确保服務端和(hé)客戶端使用(yòng)同樣的随機數種子 (比如把種子包含在序列化的狀态中,然後在客戶端取回)。

服務端和(hé)客戶端的時(shí)區(qū)不一緻。有時(shí)候我們可能(néng)會(huì)想要把一個時(shí)間轉換爲用(yòng)戶的當地時(shí)間,但(dàn)在服務端的時(shí)區(qū)跟用(yòng)戶的時(shí)區(qū)可能(néng)并不一緻,我們也(yě)并不能(néng)可靠的在服務端預先知(zhī)道(dào)用(yòng)戶的時(shí)區(qū)。這(zhè)種情況下(xià),當地時(shí)間的轉換也(yě)應該作(zuò)爲純客戶端邏輯去執行。

當 Vue 遇到(dào)激活不匹配時(shí),它将嘗試自(zì)動恢複并調整預渲染的 DOM 以匹配客戶端的狀态。這(zhè)将導緻一些(xiē)渲染性能(néng)的損失,因爲需要丢棄不匹配的節點并渲染新的節點,但(dàn)大(dà)多數情況下(xià),應用(yòng)應該會(huì)如預期一樣繼續工(gōng)作(zuò)。盡管如此,最好(hǎo)還是在開(kāi)發過程中發現(xiàn)并避免激活不匹配。

自(zì)定義指令 ​

因爲大(dà)多數的自(zì)定義指令都包含了(le)對(duì) DOM 的直接操作(zuò),所以它們會(huì)在 SSR 時(shí)被忽略。但(dàn)如果你(nǐ)想要自(zì)己控制一個自(zì)定義指令在 SSR 時(shí)應該如何被渲染 (即應該在渲染的元素上(shàng)添加哪些(xiē) attribute),你(nǐ)可以使用(yòng) getSSRProps 指令鈎子:

js

const myDirective = {

mounted(el, binding) {

// 客戶端實現(xiàn):

// 直接更新 DOM

el.id = binding.value

},

getSSRProps(binding) {

// 服務端實現(xiàn):

// 返回需要渲染的 prop

// getSSRProps 隻接收一個 binding 參數

return {

id: binding.value

}

}

}

Teleports ​

在 SSR 的過程中 Teleport 需要特殊處理(lǐ)。如果渲染的應用(yòng)包含 Teleport,那麽其傳送的内容将不會(huì)包含在主應用(yòng)渲染出的字符串中。在大(dà)多數情況下(xià),更推薦的方案是在客戶端挂載時(shí)條件式地渲染 Teleport。

如果你(nǐ)需要激活 Teleport 内容,它們會(huì)暴露在服務端渲染上(shàng)下(xià)文(wén)對(duì)象的 teleports 屬性下(xià):

js

const ctx = {}

const html = await renderToString(app, ctx)

console.log(ctx.teleports) // { '#teleported': 'teleported content' }

跟主應用(yòng)的 HTML 一樣,你(nǐ)需要自(zì)己将 Teleport 對(duì)應的 HTML 嵌入到(dào)最終頁面上(shàng)的正确位置處。

TIP

請(qǐng)避免在 SSR 的同時(shí)把 Teleport 的目标設爲 body——通常 <body> 會(huì)包含其他(tā)服務端渲染出來(lái)的内容,這(zhè)會(huì)使得 Teleport 無法确定激活的正确起始位置。

推薦用(yòng)一個獨立的隻包含 teleport 的内容的容器,例如 <div id="teleported"></div>。

網站(zhàn)建設開(kāi)發|APP設計(jì)開(kāi)發|小(xiǎo)程序建設開(kāi)發
下(xià)一篇:生産部署
上(shàng)一篇:測試