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(createServer、ssrLoadModule、ssrManifest),让上层框架构建自己的 SSR 方案。直接用这些 API 写 SSR 门槛较高,通常选择:
- Nuxt 3(Vue 全栈框架,默认 SSR)
- SvelteKit(Svelte 全栈)
- Remix / React Router v7(React,默认 SSR)
- Astro(多框架混合,默认 SSG)
- Qwik City(Qwik 的路由+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"
}
- client build:浏览器资源(JS/CSS/图片),带 hash。
- server build:Node 可直接 require 的 SSR 入口。
- ssrManifest:映射"某个路由渲染需要哪些客户端资源",用来生成
<link rel="modulepreload">,解决 CLS(布局抖动)。
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"统一抽象成"环境",每个环境有独立的插件、解析、缓存。目的:
- 更干净地支持 Cloudflare Workers / Deno / Bun 等非标准 Node 运行时。
- 多环境共享插件链时不再互相干扰。
- 为未来"任意数量环境"做准备(e.g. 同构 React Server Components 的三环境模型)。
defineConfig({
environments: {
client: { /* ... */ },
ssr: { /* ... */ },
// 自定义
worker: {
resolve: { conditions: ['worker'] },
},
},
});
对普通开发者而言,日常 Vue/React SSR 用上层框架就够——Environment API 主要影响框架作者。
8.8 SSG:vite-plugin-ssg 与 Astro
Vite 本身不做 SSG,但生态里有插件:
- vite-ssg(Vue 生态):基于 Vue + Vite 的 SSG 方案。
- Astro:完全围绕 Vite 构建的 SSG 框架,多框架组件并存,适合内容站。
- vitepress:Vue 官方的文档站工具,Vite 驱动。
8.9 选型建议
| 场景 | 推荐 |
|---|---|
| SPA(后台系统、SaaS 面板) | 纯 Vite(不用 SSR) |
| Vue 全栈 | Nuxt 3 |
| React 全栈 | Remix / React Router v7 |
| Svelte 全栈 | SvelteKit |
| 文档/博客/营销页 | Astro 或 VitePress |
| 自定义架构 | 自己用 Vite SSR API |
8.10 小结
- Vite 只提供 SSR 底层 API(createServer/ssrLoadModule/ssrManifest),不是 SSR 框架。
- 典型 SSR 项目需要两个入口(client/server)、两次 build、一个 Node 服务器。
- 绝大多数业务直接选 Nuxt/Remix/SvelteKit/Astro 等上层框架。
- Vite 6 的 Environment API 是未来"多运行时 / React RSC"的基础,日常用户不必强学。