服务端渲染(SSR)指服务器在请求时执行JS生成完整HTML返回,核心是renderToString等函数在Node.js中运行且组件须无浏览器依赖;CSR则仅返回空壳HTML,由浏览器下载JS后渲染,首屏慢、SEO差;SSR非必然更快,需严格同构、数据一致与环境隔离。

JavaScript 服务端渲染(SSR)不是指“用 Node.js 跑 React/Vue”,而是指:在用户请求页面时,服务器执行 JavaScript 渲染逻辑,生成完整的 HTML 字符串并返回,而不是只发一个空壳 index.html 让浏览器自己拉 JS、再渲染。
服务端渲染的核心是 renderToString 或等效函数
以 React 为例,关键动作是调用 ReactDOMServer.renderToString()(或 renderToPipeableStream()),把组件树同步/流式转成 HTML 字符串;Vue 则用 vue-server-renderer 的 renderToString()。这些函数必须运行在 Node.js 环境中,且组件不能依赖浏览器全局对象(如 window、document)。
常见错误现象:
- 直接在 SSR 中调用
useEffect或componentDidMount里的 DOM 操作 —— 会报ReferenceError: document is not defined - 在
renderToString前未正确注入初始数据(如 Redux state、路由匹配结果),导致首屏 HTML 缺内容 - 使用了不支持 SSR 的第三方组件(比如某些图表库硬绑
canvas)
客户端渲染(CSR)的典型流程是静态 HTML + 挂载
传统 CSR 应用打包后只有一个极简的 index.html,里面只有 和一个 标签。浏览器下载 JS 后才开始解析组件、发起 API 请求、构建 DOM —— 这意味着首屏白屏时间长、SEO 不友好、首屏无法直出内容。
立即学习“Java免费学习笔记(深入)”;
关键差异点:
- CSR 的
ReactDOM.hydrateRoot()(或旧版hydrate())只是“激活”已存在的 HTML,不做真实渲染;而 SSR 的renderToString()是真渲染 - CSR 中所有副作用(数据获取、动画初始化)都发生在浏览器端;SSR 要求数据获取提前到服务端完成(如 Next.js 的
getServerSideProps) - CSR 的路由跳转基本不触发完整页面刷新;SSR 每次 HTTP 请求都可能对应一次服务端渲染(除非配合客户端路由接管)
SSR 不等于“更快”,它引入了新的瓶颈和约束
SSR 把渲染压力从用户设备转移到服务器,但换来的是更复杂的部署、更高的内存/CPU 开销、以及对代码同构性的严苛要求。比如:
- Node.js 进程渲染一个复杂页面可能耗时 100–300ms,而 CSR 首屏 JS 加载+执行可能只要 80ms(取决于网络和设备)
- 你必须确保服务端和客户端使用完全相同的组件版本、相同的 props、相同的初始状态,否则会触发 React 的 hydration mismatch 警告甚至错误(
Text content does not match) - 像
localStorage、navigator.geolocation这类纯浏览器 API,不能在 SSR 阶段访问 —— 必须包裹在if (typeof window !== 'undefined')或生命周期钩子中
if (typeof window !== 'undefined') {
// 这里才能安全使用 localStorage
const savedTheme = localStorage.getItem('theme');
}
现代框架如何降低 SSR 使用门槛
Next.js、Nuxt、Remix 等框架封装了服务端渲染的底层细节,但它们仍强制你区分“服务端可执行”和“仅客户端可用”的代码位置:
- Next.js 中,
getServerSideProps函数内可以发请求、读文件、连接数据库;但它不能包含任何浏览器 API 调用 - Nuxt 的
asyncData和fetch钩子会在服务端和客户端都执行,但服务端执行结果会序列化进 HTML 的,避免客户端重复请求 - Remix 的
loader函数天然只运行在服务端,数据不会泄漏到客户端,安全性更高
真正难的不是“怎么开启 SSR”,而是怎么让组件既能在 Node.js 里跑通,又能在浏览器里正确 hydrate —— 很多 bug 都藏在数据一致性、副作用时机、环境判断漏判里。











