跳至內容

伺服器端渲染

注意

SSR 專門指支援在 Node.js 中執行相同應用程式的前端框架(例如 React、Preact、Vue 和 Svelte),將其預先渲染為 HTML,最後在客戶端進行水合作用。如果您正在尋找與傳統伺服器端框架的整合,請查看後端整合指南

以下指南也假設您先前有使用所選框架進行 SSR 的經驗,並且只會著重於 Vite 特定的整合細節。

底層 API

這是一個適用於程式庫和框架作者的底層 API。如果您的目標是建立應用程式,請務必先查看 Awesome Vite SSR 區塊中的高階 SSR 外掛和工具。也就是說,許多應用程式都是直接在 Vite 原生的底層 API 之上成功建置的。

目前,Vite 正在透過 環境 API 開發改進的 SSR API。請查看連結以了解更多詳細資訊。

協助

如果您有任何疑問,社群通常會在 Vite Discord 的 #ssr 頻道提供協助。

範例專案

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 API

index.html 需要參考 entry-client.js 並包含一個佔位符,用於注入伺服器渲染的標記

index.html
html
<div id="app"><!--ssr-outlet--></div>
<script type="module" src="/src/entry-client.js"></script>

您可以使用任何您偏好的佔位符來代替 <!--ssr-outlet-->,只要它可以精確地被取代。

條件邏輯

如果您需要根據 SSR 與客戶端執行條件邏輯,您可以使用

js
if (import.meta.
env
.
SSR
) {
// ... server only logic }

這會在建置期間靜態取代,因此它允許對未使用的分支進行 tree-shaking。

設定開發伺服器

在建置 SSR 應用程式時,您可能希望完全控制您的主要伺服器,並將 Vite 與生產環境分離。因此,建議在 Middleware 模式下使用 Vite。以下是使用 express (v4) 的範例

server.js
js
import 
fs
from 'node:fs'
import
path
from 'node:path'
import {
fileURLToPath
} from 'node:url'
import
express
from 'express'
import {
createServer
as
createViteServer
} from 'vite'
const
__dirname
=
path
.
dirname
(
fileURLToPath
(import.meta.
url
))
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
('*', async (
req
,
res
) => {
// serve index.html - we will tackle this next })
app
.
listen
(5173)
}
createServer
()

此處的 viteViteDevServer 的一個實例。vite.middlewares 是一個 Connect 實例,可用作任何相容於 connect 的 Node.js 框架中的 middleware。

下一步是實作 * 處理程序,以提供伺服器渲染的 HTML

server.js
js
app
.
use
('*', async (
req
,
res
,
next
) => {
const
url
=
req
.
originalUrl
try { // 1. Read index.html let
template
=
fs
.
readFileSync
(
path
.
resolve
(
__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 指令碼也應該變更為使用伺服器指令碼

package.json
diff
  "scripts": {
-   "dev": "vite"
+   "dev": "node server"
  }

建置生產環境

若要發佈用於生產環境的 SSR 專案,我們需要

  1. 照常產生客戶端建置;
  2. 產生 SSR 建置,該建置可以直接透過 import() 載入,因此我們不必經過 Vite 的 ssrLoadModule

我們在 package.json 中的指令碼會像這樣

package.json
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 來新增一些生產環境特定的邏輯

  • 使用 dist/client/index.html 作為範本,而不是讀取根 index.html,因為它包含指向客戶端建置的正確資源連結。

  • 使用 import('./dist/server/entry-server.js'),而不是 await vite.ssrLoadModule('/src/entry-server.js') (此檔案是 SSR 建置的結果)。

  • vite 開發伺服器的建立和所有使用都移至僅限於開發的條件分支之後,然後新增靜態檔案提供 middleware,以提供來自 dist/client 的檔案。

請參閱範例專案,以取得可運作的設定。

產生預先載入指令

vite build 支援 --ssrManifest 旗標,這會在建置輸出目錄中產生 .vite/ssr-manifest.json

diff
- "build:client": "vite build --outDir dist/client",
+ "build:client": "vite build --outDir dist/client --ssrManifest",

上述指令碼現在將為客戶端建置產生 dist/client/.vite/ssr-manifest.json (是的,SSR 資訊清單是從客戶端建置產生的,因為我們想要將模組 ID 對應到客戶端檔案)。資訊清單包含模組 ID 與其相關區塊和資源檔案的對應。

若要利用資訊清單,框架需要提供一種方法來收集伺服器渲染呼叫期間使用的元件的模組 ID。

@vitejs/plugin-vue 開箱即用支援此功能,並會自動在相關的 Vue SSR 環境中註冊已使用的元件模組 ID

src/entry-server.js
js
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 函數。這會為我們提供足夠的資訊來呈現用於非同步路由的檔案的預先載入指令!請參閱 demo 原始碼,以取得完整的範例。您也可以將此資訊用於 103 早期提示

預先渲染 / SSG

如果事先知道路由和某些路由所需的資料,我們可以使用與生產環境 SSR 相同的邏輯,將這些路由預先渲染為靜態 HTML。這也可以視為一種靜態網站產生 (SSG)。請參閱 demo 預先渲染指令碼,以取得可運作的範例。

SSR 外部資源

執行 SSR 時,預設情況下,相依性會從 Vite 的 SSR 轉換模組系統中「外部化」。這會加速開發和建置。

如果相依性需要由 Vite 的管線轉換,例如,因為在其中使用了 Vite 功能而未進行轉譯,則可以將它們新增至 ssr.noExternal

對於連結的相依性,它們預設不會外部化,以利用 Vite 的 HMR。如果這不是您所希望的,例如,要測試相依性,就像它們未連結一樣,您可以將其新增至 ssr.external

使用別名

如果您已設定別名,將一個套件重新導向到另一個套件,您可能想要為實際的 node_modules 套件設定別名,使其能夠適用於 SSR 外部化的相依性。 Yarnpnpm 都支援透過 npm: 前綴設定別名。

SSR 特定的外掛邏輯

某些框架 (例如 Vue 或 Svelte) 會根據客戶端與 SSR 將元件編譯為不同的格式。為了支援條件轉換,Vite 會在下列外掛掛鉤的 options 物件中傳遞額外的 ssr 屬性

  • resolveId
  • load
  • transform

範例

js
export function 
mySSRPlugin
() {
return {
name
: 'my-ssr',
transform
(
code
,
id
,
options
) {
if (
options
?.
ssr
) {
// perform ssr-specific transform... } }, } }

loadtransform 中的 options 物件是可選的,rollup 目前未使用此物件,但未來可能會使用額外的中繼資料擴充這些掛鉤。

注意

在 Vite 2.7 之前,這是透過位置參數 ssr 而非使用 options 物件來告知外掛鉤子的。所有主要框架和外掛都已更新,但您可能會找到使用先前 API 的過時文章。

SSR 目標

SSR 建置的預設目標是 Node 環境,但您也可以在 Web Worker 中執行伺服器。每個平台的套件入口解析方式都不同。您可以使用設定為 'webworker'ssr.target 將目標設定為 Web Worker。

SSR 套件捆綁

在某些情況下,例如 webworker 執行環境,您可能希望將您的 SSR 建置捆綁成單一 JavaScript 檔案。您可以透過將 ssr.noExternal 設定為 true 來啟用此行為。這將執行兩件事:

  • 將所有依賴項視為 noExternal
  • 如果導入任何 Node.js 內建模組,則拋出錯誤

SSR 解析條件

預設情況下,套件入口解析將使用 resolve.conditions 中設定的條件進行 SSR 建置。您可以使用 ssr.resolve.conditionsssr.resolve.externalConditions 來客製化此行為。

Vite CLI

CLI 命令 $ vite dev$ vite preview 也可用於 SSR 應用程式。您可以使用 configureServer 將您的 SSR 中介軟體新增到開發伺服器,並使用 configurePreviewServer 將其新增到預覽伺服器。

注意

使用後置鉤子,以便您的 SSR 中介軟體在 Vite 的中介軟體之後執行。

以 MIT 許可證發布。(ccee3d7c)