Vite 配合 TypeScript 实现 SSR 的核心是分离客户端与服务端入口、利用 Vite 的 SSR 中间件和 ssrLoadModule 动态加载能力,在 Node.js 中运行框架 SSR API;目录需区分通用/客户端/服务端逻辑,TypeScript 通过 import.meta.env.SSR 做环境分支,vite.config.ts 配置 middlewareMode: 'ssr' 并指定 ssr.noExternal,server.ts 串联 Vite 中间件与自定义服务,确保路由预匹配、数据预取、元信息注入及水合一致性。

Vite 配合 TypeScript 实现 SSR,核心在于分离客户端与服务端入口、利用 Vite 的 SSR 中间件能力、并在 Node.js 环境中运行 Vue/React 等框架的 SSR API。整个流程不依赖打包器(如 Webpack),而是靠 Vite 的原生 ESM 支持和 ssrLoadModule 动态加载能力完成服务端渲染。
关键点不是“把 Vite 当打包工具用”,而是让它成为开发时的 SSR 协同引擎——它负责转换源码、注入 HMR、处理模块解析,而真正的渲染逻辑由你的框架(如 Vue 3)和自定义服务器(如 Express/Koa)驱动。
目录结构要清晰区分执行环境
一个可维护的 SSR 项目需明确划分通用逻辑、客户端专属逻辑和服务端专属逻辑:
-
src/main.ts:导出一个工厂函数,创建环境无关的应用实例(即不直接挂载,也不调用createApp()后立即.mount()) -
src/entry-client.ts:在浏览器中执行,调用main.ts创建应用并挂载到#app -
src/entry-server.ts:在 Node.js 中执行,调用main.ts创建应用,并使用框架 SSR API(如renderToString或renderSSR)生成 HTML 字符串 -
index.html:含占位节点<div id="app"><!--ssr-outlet--></div>,服务端渲染时替换该注释为实际 HTML -
server.ts:用 Express/Koa 启动 HTTP 服务,集成 Vite 中间件,读取index.html,调用entry-server.ts渲染,插入 HTML 并返回响应
TypeScript 类型需适配:import.meta.env.SSR 是编译期静态替换的布尔字面量,可用于条件分支(如只在服务端发请求、跳过 DOM 操作等)。
配置 Vite 的 SSR 开发模式
Vite 不提供开箱即用的 SSR 构建命令,需手动配置 vite.config.ts:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
ssr: {
// 显式指定服务端入口,供 build:ssr 使用(生产构建)
noExternal: ['vue', 'vue-router', 'pinia']
},
server: {
middlewareMode: 'ssr' // 关键:启用 SSR 中间件模式
}
})开发时,middlewareMode: 'ssr' 让 Vite 不起自己的 HTML 服务,而是暴露 vite.middlewares 给 Express/Koa 使用。这样你既能享受 Vite 的热更新和模块解析,又能完全控制 HTTP 生命周期。
服务端渲染逻辑要支持上下文与水合一致性
entry-server.ts 必须返回 Promise,且需确保:
- 路由预匹配(如 Vue Router 的
router.push()+router.isReady()) - 数据预取(如组件中
async setup()或onServerPrefetch) - 元信息注入(如
<title>、<meta>标签,通过useHead或renderMeta) - 生成的 HTML 与客户端水合时 DOM 结构严格一致(否则 Vue 会抛 warning 并降级为 CSR)
示例(Vue 3 + TypeScript):
// src/entry-server.ts
import { createSSRApp } from 'vue'
import { renderToString } from 'vue/server-renderer'
import App from './App.vue'
import { createRouter } from './router'
export async function render(url: string) {
const app = createSSRApp(App)
const router = createRouter()
router.push(url)
await router.isReady()
// 可在此处触发数据预取逻辑(如 store.dispatch('fetchPost'))
const html = await renderToString(app)
return html
}注意:renderToString 返回的是完整 HTML 字符串,不含 <html> 和 <body> 标签——它只渲染 App 组件内部内容,因此必须由你拼接到 index.html 模板中。
开发服务器需串联 Vite + 自定义 Node 服务
server.ts 是 SSR 的调度中心,典型流程是:
- 启动 Express/Koa 实例
- 加载 Vite 中间件(处理
/@modules/xxx、HMR、TS 转换等) - 对每个请求:
- 读取原始
index.html - 用
vite.transformIndexHtml()注入开发脚本(如 HMR client) - 动态
vite.ssrLoadModule('./src/entry-server.ts')加载渲染函数 - 执行
render(req.url)获取 HTML 片段 - 替换
<!--ssr-outlet-->为渲染结果 - 返回完整 HTML 响应
- 读取原始
这个过程全程保持 TypeScript 类型安全——Vite 的 ssrLoadModule 会自动转译 .ts 文件为 Node.js 可执行代码,无需额外配置 ts-node 或 esbuild-register。
TypeScript 和 Vite 的 SSR 组合,本质是让类型系统覆盖前后端共用逻辑,同时借 Vite 的现代模块能力规避传统打包痛点。只要入口拆分清楚、渲染时机可控、HTML 插入精准,SSR 就不是黑盒,而是一套可调试、可测试、可渐进增强的渲染策略。


















