跳至内容

服务器端渲染

注意

SSR 特指支持在 Node.js 中运行相同应用程序、将其预渲染为 HTML,最后在客户端进行水合的 前端框架(例如 React、Preact、Vue 和 Svelte)。如果您正在寻找与传统服务器端框架的集成,请查看 后端集成指南

以下指南还假设您之前有使用您选择的框架进行 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 并包含一个占位符,服务器渲染的标记应该被注入到其中

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 }

这在构建期间被静态替换,因此它将允许对未使用的分支进行树摇。

设置开发服务器

在构建 SSR 应用程序时,您可能希望完全控制您的主服务器,并将 Vite 与生产环境分离。因此,建议在中间件模式下使用 Vite。以下是一个使用 express 的示例

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 框架中的中间件。

下一步是实现 * 处理程序以提供服务器渲染的 HTML

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 脚本也应该更改为使用服务器脚本

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

生产环境构建

要为生产环境发布 SSR 项目,我们需要

  1. 像往常一样生成客户端构建;
  2. 生成 SSR 构建,它可以直接通过 import() 加载,这样我们就不必再通过 Vite 的 ssrLoadModule 加载;

我们 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 添加一些特定于生产环境的逻辑

  • 不要读取根 index.html,而是使用 dist/client/index.html 作为模板,因为它包含指向客户端构建的正确资源链接。

  • 不要使用 await vite.ssrLoadModule('/src/entry-server.js'),而是使用 import('./dist/server/entry-server.js')(此文件是 SSR 构建的结果)。

  • vite 开发服务器的创建和所有使用移到仅限开发环境的条件分支中,然后添加静态文件服务中间件以提供 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

js
// src/entry-server.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 函数。这将为我们提供足够的信息来渲染用于异步路由的文件的预加载指令!请查看 演示源代码 以了解完整的示例。您还可以使用此信息来实现 103 早期提示

预渲染 / SSG

如果路由和某些路由所需的数据在预先已知,我们可以使用与生产环境 SSR 相同的逻辑将这些路由预渲染为静态 HTML。这也可以被认为是一种静态站点生成 (SSG) 形式。请查看 演示预渲染脚本 以了解工作示例。

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 构建的默认目标是节点环境,但你也可以在 Web Worker 中运行服务器。包入口解析对于每个平台都是不同的。你可以将目标配置为 Web Worker,使用 ssr.target 设置为 'webworker'

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 的中间件之后运行。