服務端渲染 (SSR)
注意
SSR 特指支援在 Node.js 中運行同一應用程式、將其預渲染為 HTML,最後在客戶端進行“注水”(hydration)的前端框架(例如 React、Preact、Vue 和 Svelte)。如果你正在尋找與傳統服務端框架的整合,請檢視後端整合指南。
本指南假設你具備所選框架中 SSR 的使用經驗,並將重點介紹 Vite 特有的整合細節。
底層 API
這是一個面向庫和框架作者的底層 API。如果你的目標是建立一個應用程式,請務必先檢視 Awesome Vite SSR 部分中的高階 SSR 外掛和工具。即便如此,許多應用程式已經成功直接構建在 Vite 的原生底層 API 之上。
目前,Vite 正在透過 環境 API (Environment API) 開發更完善的 SSR API。檢視連結以瞭解更多詳情。
示例專案
Vite 提供了對服務端渲染 (SSR) 的內建支援。create-vite-extra 包含了一些 SSR 示例配置,你可以將其作為本指南的參考:
你也可以透過執行 create-vite 並在框架選項中選擇 Others > create-vite-extra 來本地構建這些專案。
原始碼結構
一個典型的 SSR 應用程式具有以下原始碼結構:
- index.html
- server.js # main application server
- src/
- main.js # exports env-agnostic (universal) app code
- entry-client.js # mounts the app to a DOM element
- entry-server.js # renders the app using the framework's SSR APIindex.html 需要引用 entry-client.js 幷包含一個佔位符,以便注入服務端渲染的標記:
<div id="app"><!--ssr-outlet--></div>
<script type="module" src="/src/entry-client.js"></script>除了 <!--ssr-outlet-->,你可以使用任何你喜歡的佔位符,只要它能被精確替換即可。
條件邏輯
如果你需要根據 SSR 或客戶端執行條件邏輯,可以使用:
if (import.meta.env.SSR) {
// ... server only logic
}這會在構建過程中進行靜態替換,從而允許對未使用的分支進行搖樹最佳化 (tree-shaking)。
配置開發伺服器
在構建 SSR 應用時,你通常需要完全控制主伺服器並將 Vite 與生產環境解耦。因此,建議在中介軟體模式下使用 Vite。以下是一個使用 express 的示例:
import fs from 'node:fs'
import path from 'node:path'
import express from 'express'
import { createServer as createViteServer } from 'vite'
async function createServer() {
const app = express()
// Create Vite server in middleware mode and configure the app type as
// 'custom', disabling Vite's own HTML serving logic so parent server
// can take control
const vite = await createViteServer({
server: { middlewareMode: true },
appType: 'custom'
})
// Use vite's connect instance as middleware. If you use your own
// express router (express.Router()), you should use router.use
// When the server restarts (for example after the user modifies
// vite.config.js), `vite.middlewares` is still going to be the same
// reference (with a new internal stack of Vite and plugin-injected
// middlewares). The following is valid even after restarts.
app.use(vite.middlewares)
app.use('*all', async (req, res) => {
// serve index.html - we will tackle this next
})
app.listen(5173)
}
createServer()這裡的 vite 是 ViteDevServer 的一個例項。vite.middlewares 是一個 Connect 例項,可以用作任何相容 Connect 的 Node.js 框架中的中介軟體。
下一步是實現 * 處理程式以提供服務端渲染的 HTML:
app.use('*all', async (req, res, next) => {
const url = req.originalUrl
try {
// 1. Read index.html
let template = fs.readFileSync(
path.resolve(import.meta.dirname, 'index.html'),
'utf-8',
)
// 2. Apply Vite HTML transforms. This injects the Vite HMR client,
// and also applies HTML transforms from Vite plugins, e.g. global
// preambles from @vitejs/plugin-react
template = await vite.transformIndexHtml(url, template)
// 3. Load the server entry. ssrLoadModule automatically transforms
// ESM source code to be usable in Node.js! There is no bundling
// required, and provides efficient invalidation similar to HMR.
const { render } = await vite.ssrLoadModule('/src/entry-server.js')
// 4. render the app HTML. This assumes entry-server.js's exported
// `render` function calls appropriate framework SSR APIs,
// e.g. ReactDOMServer.renderToString()
const appHtml = await render(url)
// 5. Inject the app-rendered HTML into the template.
const html = template.replace(`<!--ssr-outlet-->`, () => appHtml)
// 6. Send the rendered HTML back.
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
} catch (e) {
// If an error is caught, let Vite fix the stack trace so it maps back
// to your actual source code.
vite.ssrFixStacktrace(e)
next(e)
}
})package.json 中的 dev 指令碼也應該更改為使用伺服器指令碼:
"scripts": {
- "dev": "vite"
+ "dev": "node server"
}生產構建
要釋出 SSR 專案用於生產,我們需要:
- 像往常一樣構建客戶端;
- 構建 SSR 程式碼,使其可以透過
import()直接載入,這樣我們就無需經過 Vite 的ssrLoadModule;
我們在 package.json 中的指令碼如下所示:
{
"scripts": {
"dev": "node server",
"build:client": "vite build --outDir dist/client",
"build:server": "vite build --outDir dist/server --ssr src/entry-server.js"
}
}注意 --ssr 標誌,它表示這是一個 SSR 構建。它還應該指定 SSR 入口。
然後,在 server.js 中,我們需要透過檢查 process.env.NODE_ENV 來新增一些生產環境特定的邏輯:
不再讀取根目錄下的
index.html,而是使用dist/client/index.html作為模板,因為它包含了指向客戶端構建的正確資源連結。使用
import('./dist/server/entry-server.js')來代替await vite.ssrLoadModule('/src/entry-server.js')(該檔案是 SSR 構建的結果)。將
vite開發伺服器的建立和所有使用邏輯移至僅開發環境的條件分支中,然後新增靜態檔案服務中介軟體以從dist/client提供檔案。
請參考示例專案以獲得可執行的配置。
生成預載入指令
vite build 支援 --ssrManifest 標誌,它將在構建輸出目錄中生成 .vite/ssr-manifest.json:
- "build:client": "vite build --outDir dist/client",
+ "build:client": "vite build --outDir dist/client --ssrManifest",上面的指令碼現在將為客戶端構建生成 dist/client/.vite/ssr-manifest.json(是的,SSR 清單是從客戶端構建生成的,因為我們需要將模組 ID 對映到客戶端檔案)。該清單包含模組 ID 到其關聯塊 (chunks) 和資原始檔的對映。
為了利用該清單,框架需要提供一種方法來收集在服務端渲染呼叫期間使用的元件模組 ID。
@vitejs/plugin-vue 開箱即用地支援此功能,並自動將已使用的元件模組 ID 註冊到關聯的 Vue SSR 上下文中:
const ctx = {}
const html = await vueServerRenderer.renderToString(app, ctx)
// ctx.modules is now a Set of module IDs that were used during the render在 server.js 的生產分支中,我們需要讀取清單並將其傳遞給 src/entry-server.js 匯出的 render 函式。這將為我們提供足夠的資訊來為非同步路由使用的檔案渲染預載入指令!請參閱演示原始碼以獲取完整示例。你還可以將此資訊用於 103 Early Hints。
預渲染 / SSG
如果路由以及特定路由所需的資料是預先知道的,我們可以使用與生產環境 SSR 相同的邏輯將這些路由預渲染為靜態 HTML。這也可以被視為靜態站點生成 (SSG) 的一種形式。請參閱演示預渲染指令碼以獲取可執行示例。
SSR 外部化 (Externals)
在執行 SSR 時,依賴項預設會從 Vite 的 SSR 轉換模組系統中“外部化”。這加快了開發和構建速度。
如果某個依賴項需要經過 Vite 的流水線轉換(例如,因為 Vite 特性在其中未經轉換地使用),則可以將它們新增到 ssr.noExternal 中。
對於連結的依賴項,為了利用 Vite 的 HMR,它們預設不會被外部化。如果不需要這樣做(例如,為了測試依賴項是否被連結),可以將其新增到 ssr.external 中。
使用別名 (Aliases)
如果你配置了將一個包重定向到另一個包的別名,你可能希望為 node_modules 中的包設定別名,以便使 SSR 外部化的依賴項正常工作。Yarn 和 pnpm 都支援透過 npm: 字首進行別名設定。
SSR 特定的外掛邏輯
一些框架(如 Vue 或 Svelte)會根據客戶端與 SSR 將元件編譯為不同的格式。為了支援條件轉換,Vite 在以下外掛鉤子的 options 物件中傳遞了一個額外的 ssr 屬性:
resolveIdloadtransform
示例
export function mySSRPlugin() {
return {
name: 'my-ssr',
transform(code, id, options) {
if (options?.ssr) {
// perform ssr-specific transform...
}
},
}
}load 和 transform 中的 options 物件是可選的,Rollup 目前未使用此物件,但未來可能會使用附加元資料擴充套件這些鉤子。
注意
在 Vite 2.7 之前,外掛鉤子是透過位置引數 ssr 而不是使用 options 物件來獲得此資訊的。所有主流框架和外掛都已更新,但你可能會發現使用舊 API 的過時文章。
SSR 目標 (Target)
SSR 構建的預設目標是 Node 環境,但你也可以在 Web Worker 中執行伺服器。每個平臺的包入口解析方式都不同。你可以透過將 ssr.target 設定為 'webworker' 來將目標配置為 Web Worker。
SSR 捆綁 (Bundle)
在某些情況下(如 webworker 執行時),你可能希望將 SSR 構建捆綁到一個單獨的 JavaScript 檔案中。你可以透過設定 ssr.noExternal 為 true 來啟用此行為。這將執行兩件事:
- 將所有依賴項視為
noExternal; - 如果匯入了任何 Node.js 內建模組,則丟擲錯誤。
SSR 解析條件
預設情況下,包入口解析將使用在 resolve.conditions 中為 SSR 構建設定的條件。你可以使用 ssr.resolve.conditions 和 ssr.resolve.externalConditions 來自定義此行為。
Vite CLI
CLI 命令 $ vite dev 和 $ vite preview 也可以用於 SSR 應用。你可以透過 configureServer 將你的 SSR 中介軟體新增到開發伺服器,透過 configurePreviewServer 新增到預覽伺服器。
注意
使用後置鉤子 (post hook),以便你的 SSR 中介軟體在 Vite 的中介軟體之後執行。
