Chapter 08

SSR 与静态生成

理解 Vite 的 SSR 底层 API,认识它与 Nuxt/SvelteKit/Remix 的关系,以及 Vite 6 的 Environment API。

8.1 SSR / SSG / CSR 概念

CSR(Client-Side Rendering)
浏览器加载 JS,JS 运行时再渲染页面——SPA 的经典模式。首屏白屏时间长。
SSR(Server-Side Rendering)
服务器为每次请求生成 HTML 返回——首屏立刻可见,之后 hydrate(客户端接管)。Next/Nuxt 的常规模式。
SSG(Static Site Generation)
构建时把每个路由预渲染成静态 HTML——部署成静态文件即可,CDN 友好。适合博客、文档、营销页。
ISR(Incremental Static Regeneration)
SSG + 按需重新生成,兼顾 CDN 速度和内容时效——上层框架(Next 等)实现。

8.2 Vite 的 SSR 定位

Vite 不是 SSR 框架——它只提供底层 API(createServerssrLoadModulessrManifest),让上层框架构建自己的 SSR 方案。直接用这些 API 写 SSR 门槛较高,通常选择:

这一章重点讲 Vite 的 SSR API,以及为什么你通常不用直接碰它。

8.3 一个最小的 Vite SSR 应用

项目结构:

my-ssr/
├── index.html         # 含 <!--ssr-outlet--> 占位符
├── server.js          # Node 服务器(Express/Hono)
├── src/
│   ├── entry-client.ts  # 浏览器入口(hydrate)
│   ├── entry-server.ts  # SSR 入口(renderToString)
│   └── App.vue
└── vite.config.ts

index.html

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

entry-server.ts

import { createSSRApp } from 'vue';
import { renderToString } from 'vue/server-renderer';
import App from './App.vue';

export async function render() {
  const app = createSSRApp(App);
  const html = await renderToString(app);
  return { html };
}

entry-client.ts

import { createSSRApp } from 'vue';
import App from './App.vue';
createSSRApp(App).mount('#app');

server.js(开发模式)

import express from 'express';
import { createServer } from 'vite';
import fs from 'node:fs/promises';

const app = express();
const vite = await createServer({
  server: { middlewareMode: true },
  appType: 'custom',
});
app.use(vite.middlewares);

app.use('*', async (req, res) => {
  let template = await fs.readFile('index.html', 'utf-8');
  template = await vite.transformIndexHtml(req.originalUrl, template);
  const { render } = await vite.ssrLoadModule('/src/entry-server.ts');
  const { html } = await render();
  res.end(template.replace('<!--ssr-outlet-->', html));
});

app.listen(3000);

8.4 关键 API

createServer({ server: { middlewareMode: true } })
创建一个不监听端口的 Vite 实例,作为 Express/Hono 中间件嵌入现有服务器。
vite.ssrLoadModule(id)
按 SSR 语义加载模块(返回 Node 可执行的版本),不经过浏览器优化。
vite.ssrFixStacktrace(e)
把 SSR 异常堆栈里的编译后路径还原为源文件路径,方便调试。
vite.transformIndexHtml(url, html)
应用 index.html 的转换插件(注入脚本、替换变量等)。

8.5 生产构建:两次 build

package.json
  "scripts": {
    "build:client": "vite build --ssrManifest --outDir dist/client",
    "build:server": "vite build --ssr src/entry-server.ts --outDir dist/server",
    "build":        "npm run build:client && npm run build:server"
  }

8.6 SSR 代码写法注意点

避免访问浏览器 API
SSR 里没有 window/document/localStorage。用 typeof window !== 'undefined'import.meta.env.SSR 做环境判断。
模块副作用要谨慎
模块顶层代码在每次请求都会执行(Vite SSR 缓存后除外),不要在顶层发起数据库连接等昂贵操作。
服务器/浏览器专属依赖分开
ssr.noExternal/ssr.external 控制哪些包走 Node 原生 require,哪些交给 Vite 处理。

8.7 Environment API(Vite 6)

Vite 6 引入了 Environment API——把"客户端 / SSR / Worker / Edge"统一抽象成"环境",每个环境有独立的插件、解析、缓存。目的:

defineConfig({
  environments: {
    client: { /* ... */ },
    ssr:    { /* ... */ },
    // 自定义
    worker: {
      resolve: { conditions: ['worker'] },
    },
  },
});

对普通开发者而言,日常 Vue/React SSR 用上层框架就够——Environment API 主要影响框架作者。

8.8 SSG:vite-plugin-ssg 与 Astro

Vite 本身不做 SSG,但生态里有插件:

8.9 选型建议

场景推荐
SPA(后台系统、SaaS 面板)纯 Vite(不用 SSR)
Vue 全栈Nuxt 3
React 全栈Remix / React Router v7
Svelte 全栈SvelteKit
文档/博客/营销页Astro 或 VitePress
自定义架构自己用 Vite SSR API

8.10 小结