跳至内容

服务端渲染 (SSR)

注意

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

以下指南还假设您事先具备使用所选框架进行 SSR 的经验,并且只会关注 Vite 特定的集成细节。

底层 API

这是一个旨在为库和框架作者提供的底层 API。如果您的目标是创建一个应用程序,请务必首先查看 Awesome Vite SSR 部分中更高级别的 SSR 插件和工具。也就是说,许多应用程序已成功直接构建在 Vite 的原生底层 API 之上。

目前,Vite 正在使用环境 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 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 }

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

设置开发服务器

在构建 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
('*all', async (
req
,
res
) => {
// serve index.html - we will tackle this next })
app
.
listen
(5173)
}
createServer
()

这里的 viteViteDevServer 的一个实例。 vite.middlewares 是一个 Connect 实例,可以用作任何与 Connect 兼容的 Node.js 框架中的中间件。

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

server.js
js
app
.
use
('*all', 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 来添加一些特定于生产环境的逻辑

  • 不要读取根 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 开箱即用地支持此功能,并自动将使用的组件模块 ID 注册到关联的 Vue SSR 上下文中

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 函数。 这将为我们提供足够的信息来为异步路由使用的文件渲染预加载指令! 有关完整示例,请参见演示源码。 您还可以将此信息用于 103 Early Hints

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

在 MIT 许可下发布。 (083ff36d)