客户端渲染(CSR,Client‑Side Rendering)
使用 npm create vue@latest 或 npm create vite@latest 创建的 Vue3 项目,默认就是客户端渲染 。服务器返回的 HTML 文件几乎为空,只包含一个 <div id="app"></div> 和若干 <script> 标签。浏览器下载并执行 JavaScript 后,Vue 才会动态生成 DOM 并填充内容。

优点
- 前后端分离,开发简单。
- 页面切换无需刷新,体验接近原生应用。
- 服务器压力小(只托管静态文件)。
缺点
- 首屏加载慢:必须完成 JS 下载、解析、执行后才能看到内容。
- SEO 不友好:搜索引擎爬虫可能无法执行 JS,导致抓取不到内容。
- 需要额外处理 meta 标签(如标题、描述)。
预渲染 (Prerendering)
预渲染在构建阶段 启动无头浏览器(如 Puppeteer),访问你指定的路由,执行 Vue 代码,然后将渲染好的完整 HTML 保存为静态文件。用户请求这些路由时,直接返回预先生成的 HTML,无需在客户端执行 Vue 逻辑。
优点
- 保留 CSR 开发体验,同时解决首屏白屏和部分 SEO 问题。
- 部署简单(静态托管,如 Nginx、CDN)。
- 适合页面数量较少、内容相对固定的站点。
缺点
- 如果路由很多(成百上千),构建时间会非常长。
- 无法处理动态数据(如用户评论、实时价格),因为 HTML 在构建时就固定了。
- 对于参数化路由(
/user/:id)需要枚举所有可能的 ID,不现实。
示例
插件 @seresweb/vite-plugin-seo-prerender
vue3-mananger-prerender/vite.config.ts
js
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import vueJsx from "@vitejs/plugin-vue-jsx";
// import VueDevTools from 'vite-plugin-vue-devtools'
import seoPrerender from "@seresweb/vite-plugin-seo-prerender";
// import { createMpaPlugin } from "vite-plugin-virtual-mpa";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
// @ts-ignore
import prerenderFallback from "./plugin/vite-plugin-prerender-fallback.js";
// 预渲染路由列表(从 router 中提取需要预渲染的静态路由)
// const prerenderRoutes = ["/", "/home", "/dashboard", "/about", "/404"];
// https://vitejs.dev/config/
export default defineConfig(({ command }) => {
// const isBuild = command === "build";
return {
plugins: [
vue(),
vueJsx(),
AutoImport({
resolvers: [ElementPlusResolver()],
// 推荐:生成类型声明文件,提升 TypeScript 支持
dts: "src/auto-imports.d.ts",
}),
Components({
resolvers: [ElementPlusResolver()],
// 推荐:生成组件类型声明文件,提升 TypeScript 支持
dts: "src/components.d.ts",
}),
// VueDevTools(),
prerenderFallback({
distDir: "dist", // 可选,默认就是 'dist'
}),
seoPrerender({
routes: ["/yoy/dashboard"],
headless: "new",
executablePath: undefined,
}),
],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
build: {
outDir: "dist",
target: "es2015",
minify: true,
},
};
});
自定义插件,实现导航栏路径变化先请求目录下的html文件,没有再回退。
vue3-mananger-prerender/plugin/vite-plugin-prerender-fallback.js
js
// vite-plugin-prerender-fallback.js
import fs from 'node:fs';
import path from 'node:path';
/**
* Vite 插件:为预览服务器添加预渲染目录回退 + SPA fallback
* @param {Object} options
* @param {string} options.distDir - 构建输出目录,默认 'dist'
*/
export default function prerenderFallback(options = {}) {
const { distDir = 'dist' } = options;
const distPath = path.resolve(process.cwd(), distDir);
return {
name: 'vite-plugin-prerender-fallback',
apply: 'serve', // 仅在预览/开发服务器时生效(预览模式也是 serve)
configurePreviewServer(server) {
// 在所有中间件之前插入我们的处理逻辑
server.middlewares.use((req, res, next) => {
// 1. 跳过根路径、带扩展名的文件请求、API 等(避免不必要的文件查找)
const url = req.url;
if (
url === '/' ||
path.extname(url) !== '' || // 有扩展名,如 .js, .css
url.startsWith('/@') || // Vite 内部资源
url.startsWith('/node_modules/')
) {
return next();
}
// 2. 构造预渲染 HTML 的完整路径:dist + url + /index.html
// 例如 url = '/yoy/dashboard' → dist/yoy/dashboard/index.html
const htmlPath = path.join(distPath, url, 'index.html');
// 3. 同步检查文件是否存在(也可以使用 fs.promises.access 异步)
if (fs.existsSync(htmlPath)) {
// 存在预渲染文件,修改请求路径指向该文件,让后续静态中间件处理
req.url = path.posix.join(url, 'index.html');
return next();
}
// 4. 不存在预渲染文件,请求原样放行,最终会 fallback 到根 index.html
return next();
});
},
};
}
静态站点生成 (SSG)
SSG 是预渲染的升级版 ,它不局限于几个路由,而是将整个站点构建为静态 HTML 文件。它支持动态路由(通过 getStaticPaths 枚举所有可能参数),还能在构建时从 API 或本地文件获取数据(getStaticProps)。生成的每个页面都是完全独立的静态 HTML。
优点
- 极致的首屏速度(HTML 已包含完整内容)。
- SEO 完美(爬虫直接看到内容)。
- 依然保留 Vue 组件的开发体验。
- 部署成本低(CDN 即可)。
缺点
- 构建时间随页面数量线性增长,不适合超大规模站点(百万级)。
- 每次内容更新都需要重新构建全站。
- 无法处理用户个性化内容(除非配合客户端 JavaScript 二次获取)。
服务端渲染 (SSR)
SSR 下,当用户请求一个页面时,服务器动态运行 Vue 组件,生成完整的 HTML 字符串并返回给浏览器。客户端接收到 HTML 后立即显示,然后"激活"(hydrate)Vue 组件使其可交互。每次请求都重新渲染,因此可以展示最新数据。
优点
- 完美的 SEO,任何爬虫都能看到完整内容。
- 首屏速度快(HTML 已就绪,无需等待 JS 执行)。
- 支持实时数据(用户登录态、最新评论、个性化推荐)。
缺点
- 服务器压力大(每次请求都需要 CPU 渲染组件)。
- 复杂度高:需要处理 Node.js 环境、内存泄漏、缓存策略、防止双端状态不一致。
- TTFB(首字节时间)可能比静态文件高。