迁移:从 SSG(旧方案)升级到请求期 SSR(新方案)
你是代码迁移 Agent。本指南把已经做过 SSG 改造(vite-react-ssg)的项目,迁移到标准 React Router 7 data-mode 请求期 SSR。
定位:SSG 是旧方案,SSR 是新方案。 这是一次升级迁移,不是平级切换。好消息(省一大半):SSG 改造时已做的 SSR 安全工作——顶层
- SSG(旧):构建期把页面预渲染成静态
.html。只能渲染构建期就确定的内容;数据一变就得重新构建,做不到”请求期带实时数据”。- SSR(新):请求期在服务端渲染,能带动态/实时数据(DB 驱动、每次请求都新)。是 SSG 的超集 + 升级。 何时迁:所有
vite-react-ssgSSG 项目都要迁到 SSR——它已是退役旧栈(钉死 react-router v6、不再投入),不论页面内容是动态还是纯静态都升级。不要再以”内容是静态的 / SSG 够用”为由保留 SSG。SSR 是 SSG 的超集:静态页迁过去就是没有 loader 的 SSR,首屏照样在服务端预渲染出完整内容、SEO/抓取一点不受影响。
window/document 守卫、Sonner/主题的 mounted 守卫、framer-motion initial=false 首屏可见、非确定性值稳定化——SSR 完全复用、原样保留(SSR 与 SSG 是同一批 import 期 + hydration 坑)。所以本迁移主要是换渲染机制 + 升路由,护栏不用重做,比从纯 CSR 起步更省。
目标产物:标准 RR7 data-mode SSR 工程,与 CSR→SSR 完全一致——entry-server/entry-client、vite.config.ssr.ts、单一 routes.tsx、route loader。这些模板逐字见 CSR→SSR 指南 §C;本指南只讲 SSG 特有的 delta。
A. 必须遵守(MUST)
- 必须完全拆掉 SSG 机制:移除
vite-react-ssg(依赖 +dev/build脚本 + 相关 vite 插件)、main.tsx里的ViteReactSSG入口、所有getStaticPaths。 - 必须把路由从 RR6 的
RouteRecord[]/createRoutesFromElements迁到 RR7 data-mode 的RouteObject[](单一src/routes.tsx)。 - 必须把
react-router-dom@6升到react-router@7:v6 被钉死是因为vite-react-ssg要求;既然本次离开 SSG,这个钉死理由消失——升 v7 让全平台 SSR 项目统一一个目标(否则分裂出 v6-SSR / v7-SSR 两套)。 - 必须保留 SSG 已做的全部 SSR 安全守卫(见 SSG 指南 D/E/F 的
mounted/guard/initial=false)——它们对 SSR 同样必需,原样留、别删了重写。 - 必须按 CSR→SSR 指南 §C 加
entry-server/entry-client、vite.config.ssr.ts、改index.html入口。
B. 明确禁止(DO NOT)
- 不要残留任何
vite-react-ssg痕迹(残留的ViteReactSSG/getStaticPaths会和 RR7 data router 冲突)。 - 不要把已做好的浏览器守卫 /
mounted保护删掉重写——复用。 - 不要手写平台职责的东西(
x-ssrmarker、render-manifest、CDN 上传)——平台自动处理,见 CSR→SSR 指南 §F。
C. 执行步骤(按顺序)
1) 拆 SSG 机制
package.json:移除vite-react-ssg依赖;dev/build/preview脚本从vite-react-ssg dev|build改回标准vite/vite build(SSR 的双构建由平台release.sh见到vite.config.ssr.ts自动接管)。main.tsx:移除ViteReactSSG(routes, ...)入口(将被entry-client.tsx取代)。vite.config.ts:清掉 SSG 专属配置(如isSsrBuild分支里为 SSG 禁用的manualChunks逻辑);CDN/资源/alias 等通用配置保留。- 路由文件:移除所有
getStaticPaths(请求期 SSR 不需要构建期枚举路径,路径在请求时由 router 匹配)。
2) 路由升级:RR6 RouteRecord[] → RR7 RouteObject[]
- 依赖:
react-router-dom@^6.30.3→react-router@^7(v7 起合并成react-router单包)。 - 把
createRoutesFromElements(<Route/>)产出的RouteRecord[](本就是RouteObject的超集)整理成显式src/routes.tsx的RouteObject[]:element/children/path大多照搬,去掉getStaticPaths等 SSG 专属字段。 - 导入路径:v6 的
react-router-dom→ v7react-router(StaticRouter/data API 在 v7 都在react-router)。
3) 应用 SSR 模板(同 CSR→SSR)
- 照 CSR→SSR 指南 §C:加
entry-server.tsx、entry-client.tsx、vite.config.ssr.ts,index.html入口改指entry-client.tsx,App 的 providers 收进routes.tsx根布局。 - 局部深化:原来靠
getStaticPaths预取 / 客户端fetch的数据,改成 routeloader(服务端取数,见 CSR→SSR §D),组件useLoaderData()读。
D. 复用清单(SSG 已做、SSR 留着别动)
下面这些 SSG 改造时就收敛过的,SSR 同样需要,原样保留:- 顶层 / Hook 初始值里的
window/document/localStorage守卫。 Sonner/toast 的mounted守卫、主题(useTheme)的挂载一致性。framer-motioninitial=false首屏可见、useInView默认可见。- 非确定性值(时间戳/随机)的稳定化。
E. 交付验收(必须全部通过)
- 项目里无
vite-react-ssg残留(依赖/脚本/getStaticPaths/ViteReactSSG全清)。 pnpm build成功 + 产出dist/server/entry-server.js(同 CSR→SSR 验收)。- 路由是单一
src/routes.tsx的RouteObject[](RR7)。 - 原 SSG 的 SSR 安全守卫仍在;首屏 hydration 无显著 mismatch。
- 动态页通过
loader在 SSR 首屏带真实数据;纯静态页正常出壳 + 客户端接管。
F. 失败处理优先级(按顺序排查)
- 先查
vite-react-ssg是否拆干净(残留的ViteReactSSG/getStaticPaths会和 RR7 data router 冲突)。 - 再查 RR6→7 导入路径(
react-router-dom→react-router)与react-router单份 alias。 - 其余同 CSR→SSR 指南 §I 失败处理。

