跳至内容

服务器端渲染

注意

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 并选择框架选项下的 其他 > 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 与生产环境解耦。因此,建议在中间件模式下使用 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 框架中用作中间件。

下一步是实现 * 处理程序以提供服务器渲染的 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 添加一些特定于生产环境的逻辑

  • 不要读取根 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 早期提示

预渲染/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 构建的默认目标是 node 环境,但您也可以在 Web Worker 中运行服务器。每个平台的包入口解析方式都不同。您可以将目标配置为 Web Worker,方法是将 ssr.target 设置为 'webworker'

SSR 捆绑

在某些情况下(例如 webworker 运行时),您可能希望将 SSR 构建捆绑到单个 JavaScript 文件中。您可以通过将 ssr.noExternal 设置为 true 来启用此行为。这将执行两件事

  • 将所有依赖项视为 noExternal
  • 如果导入了任何 Node.js 内置模块,则会抛出错误

SSR 解析条件

默认情况下,包入口解析将使用 SSR 构建中 resolve.conditions 中设置的条件。您可以使用 ssr.resolve.conditionsssr.resolve.externalConditions 来自定义此行为。

Vite CLI

CLI 命令 $ vite dev$ vite preview 也可用于 SSR 应用。您可以使用 configureServer 将您的 SSR 中间件添加到开发服务器,并使用 configurePreviewServer 将其添加到预览服务器。

注意

使用后置钩子,以便您的 SSR 中间件在 Vite 的中间件之后运行。

在 MIT 许可证下发布。(ccee3d7c)