静态同构渲染指页面在构建时预生成完整HTML(SSG),同时保留客户端hydration能力,实现首屏零JS可读、启用JS后升级为SPA;其核心是getStaticProps与hydration边界清晰且服务端HTML真正“零JS可运行”。

静态同构渲染不是“SSR + SSG”的简单拼凑,而是让同一套组件在构建时可预生成、在运行时可接管、在爬虫眼里是完整 HTML、在用户手里是 SPA——关键在于 getStaticProps 与客户端 hydration 的边界是否干净,以及服务端输出的 HTML 是否真正“零 JS 可运行”。
什么是静态同构渲染(不是 SSR,也不是纯 SSG)
它指页面在构建阶段(build time)就完成 HTML 预渲染(SSG),但保留完整的客户端水合(hydration)能力:首屏由静态 HTML 直出,无 JS 也能展示基础内容;JS 加载后,React/Vue 等框架才“唤醒”DOM 节点,接管事件、状态和后续路由。这不同于 getServerSideProps(每次请求都 render)、也不同于无水合的纯静态页(无法交互)。
典型表现是:View Source 看到完整带数据的 HTML;禁用 JS 后页面仍可读;启用 JS 后按钮变可点、导航变无刷新。
Next.js 中实现静态同构的关键配置组合
Next.js 是目前最接近开箱即用静态同构的方案,但默认行为容易误配。必须同时满足三个条件:
-
getStaticProps返回props,且不依赖 request 对象(如req、res、cookies)——否则会退化为 SSR - 页面不使用
getServerSideProps或getInitialProps(后者已废弃且破坏静态性) - 确保
next export可成功执行,输出中不含server-only模块或动态fetch(如未加cache: 'force-cache'的 RSC fetch) - 若用 App Router,需确认 layout 和 page 组件未引入
use client以外的客户端专属 API(如window、localStorage)——这些必须包裹在useEffect或事件回调里
错误示例:getStaticProps 中调用 fetch('/api/user', { cache: 'no-store' }) → 构建失败或 fallback 到 SSR;正确写法是显式加 cache: 'force-cache' 或移至客户端获取。
Vue 项目如何逼近静态同构效果(Nuxt 3 + Nitro)
Nuxt 3 默认走的是“构建时静态生成 + 客户端 hydration”,但它不像 Next.js 那样严格区分数据获取时机。容易踩的坑集中在:
-
useAsyncData或useFetch默认开启server: true,但若其内部依赖 runtime 环境(比如基于event.context的动态 API 地址),会导致预渲染失败 -
definePageMeta({ ssr: false })会强制关闭服务端渲染,整个页面变成 CSR,失去首屏 HTML —— 这不是静态同构,是倒退 - Nitro 输出的
.output/public目录下必须存在对应路径的index.html,且其中<div id="__nuxt"> 内有真实内容(而非空 div);否则 hydration 会因 DOM 不匹配报错 <code>Hydration failed because the initial UI does not match what was rendered on the server验证方式:本地运行
nuxi preview,curl -s http://localhost:3000 | head -20,确认返回的是含业务数据的 HTML,而非仅骨架。静态同构最容易被忽略的性能断点
很多人以为“生成了 HTML 就万事大吉”,但实际线上常卡在三个隐形环节:
- 客户端 JS 包过大,导致 hydration 延迟超过 500ms,用户点击无响应 —— 建议用
next build --profile或web-vitals监控INP和TTFB,而非只看 LCP - 预渲染 HTML 中混入了未 escape 的用户输入(如评论内容),造成 XSS 漏洞;而 hydration 后框架又自动解码,导致双渲染不一致
- CDN 缓存策略未区分静态 HTML 与动态资源:比如把
/_next/static/chunks/xxx.js和/product/123/index.html设成相同缓存头,结果 HTML 更新了但 JS 还在用旧版,hydration 失败白屏
真正难的不是“怎么生成 HTML”,而是让 HTML、JS、CSS、HTTP 头四者在构建、部署、加载、执行各阶段保持语义一致 —— 这才是静态同构落地时最常崩塌的一环。
- 客户端 JS 包过大,导致 hydration 延迟超过 500ms,用户点击无响应 —— 建议用











