跳转到主要内容

迁移:从 SSG(旧方案)升级到请求期 SSR(新方案)

你是代码迁移 Agent。本指南把已经做过 SSG 改造vite-react-ssg)的项目,迁移标准 React Router 7 data-mode 请求期 SSR
定位:SSG 是旧方案,SSR 是新方案。 这是一次升级迁移,不是平级切换。
  • SSG(旧):构建期把页面预渲染成静态 .html。只能渲染构建期就确定的内容;数据一变就得重新构建,做不到”请求期带实时数据”。
  • SSR(新):请求期在服务端渲染,能带动态/实时数据(DB 驱动、每次请求都新)。是 SSG 的超集 + 升级何时迁所有 vite-react-ssg SSG 项目都要迁到 SSR——它已是退役旧栈(钉死 react-router v6、不再投入),不论页面内容是动态还是纯静态都升级。不要再以”内容是静态的 / SSG 够用”为由保留 SSG。SSR 是 SSG 的超集:静态页迁过去就是没有 loader 的 SSR,首屏照样在服务端预渲染出完整内容、SEO/抓取一点不受影响。
好消息(省一大半):SSG 改造时已做的 SSR 安全工作——顶层 window/document 守卫、Sonner/主题的 mounted 守卫、framer-motion initial=false 首屏可见、非确定性值稳定化——SSR 完全复用、原样保留(SSR 与 SSG 是同一批 import 期 + hydration 坑)。所以本迁移主要是换渲染机制 + 升路由,护栏不用重做,比从纯 CSR 起步更省。 目标产物:标准 RR7 data-mode SSR 工程,与 CSR→SSR 完全一致——entry-server/entry-clientvite.config.ssr.ts、单一 routes.tsx、route loader。这些模板逐字见 CSR→SSR 指南 §C;本指南只讲 SSG 特有的 delta

A. 必须遵守(MUST)

  1. 必须完全拆掉 SSG 机制:移除 vite-react-ssg(依赖 + dev/build 脚本 + 相关 vite 插件)、main.tsx 里的 ViteReactSSG 入口、所有 getStaticPaths
  2. 必须把路由从 RR6 的 RouteRecord[] / createRoutesFromElements 迁到 RR7 data-mode 的 RouteObject[](单一 src/routes.tsx)。
  3. 必须把 react-router-dom@6 升到 react-router@7:v6 被钉死是因为 vite-react-ssg 要求;既然本次离开 SSG,这个钉死理由消失——升 v7 让全平台 SSR 项目统一一个目标(否则分裂出 v6-SSR / v7-SSR 两套)。
  4. 必须保留 SSG 已做的全部 SSR 安全守卫(见 SSG 指南 D/E/F 的 mounted/guard/initial=false)——它们对 SSR 同样必需,原样留、别删了重写
  5. 必须按 CSR→SSR 指南 §Centry-server/entry-clientvite.config.ssr.ts、改 index.html 入口。

B. 明确禁止(DO NOT)

  1. 不要残留任何 vite-react-ssg 痕迹(残留的 ViteReactSSG/getStaticPaths 会和 RR7 data router 冲突)。
  2. 不要把已做好的浏览器守卫 / mounted 保护删掉重写——复用
  3. 不要手写平台职责的东西(x-ssr marker、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.3react-router@^7(v7 起合并成 react-router 单包)。
  • createRoutesFromElements(<Route/>) 产出的 RouteRecord[](本就是 RouteObject 的超集)整理成显式 src/routes.tsxRouteObject[]element/children/path 大多照搬,去掉 getStaticPaths 等 SSG 专属字段
  • 导入路径:v6 的 react-router-dom → v7 react-routerStaticRouter/data API 在 v7 都在 react-router)。

3) 应用 SSR 模板(同 CSR→SSR)

  • CSR→SSR 指南 §C:加 entry-server.tsxentry-client.tsxvite.config.ssr.tsindex.html 入口改指 entry-client.tsx,App 的 providers 收进 routes.tsx 根布局。
  • 局部深化:原来靠 getStaticPaths 预取 / 客户端 fetch 的数据,改成 route loader(服务端取数,见 CSR→SSR §D),组件 useLoaderData() 读。

D. 复用清单(SSG 已做、SSR 留着别动)

下面这些 SSG 改造时就收敛过的,SSR 同样需要,原样保留
  • 顶层 / Hook 初始值里的 window/document/localStorage 守卫。
  • Sonner/toast 的 mounted 守卫、主题(useTheme)的挂载一致性。
  • framer-motion initial=false 首屏可见、useInView 默认可见。
  • 非确定性值(时间戳/随机)的稳定化。
→ 这些是 SSR 同样要过的护栏;SSG 那次已做,本次直接受益

E. 交付验收(必须全部通过)

  1. 项目里vite-react-ssg 残留(依赖/脚本/getStaticPaths/ViteReactSSG 全清)。
  2. pnpm build 成功 + 产出 dist/server/entry-server.js(同 CSR→SSR 验收)。
  3. 路由是单一 src/routes.tsxRouteObject[](RR7)。
  4. 原 SSG 的 SSR 安全守卫仍在;首屏 hydration 无显著 mismatch。
  5. 动态页通过 loader 在 SSR 首屏带真实数据;纯静态页正常出壳 + 客户端接管。

F. 失败处理优先级(按顺序排查)

  1. 先查 vite-react-ssg 是否拆干净(残留的 ViteReactSSG/getStaticPaths 会和 RR7 data router 冲突)。
  2. 再查 RR6→7 导入路径(react-router-domreact-router)与 react-router 单份 alias。
  3. 其余同 CSR→SSR 指南 §I 失败处理