react-spring 文档站迁移实战:从 Remix 2 到 React Router 7 框架模式
2026/9/19 22:44:42 网站建设 项目流程

react-spring 文档站迁移实战:从 Remix 2 到 React Router 7 框架模式

【免费下载链接】react-spring✌️ A spring physics based React animation library项目地址: https://gitcode.com/gh_mirrors/re/react-spring

React Router 7 的框架模式(framework mode)是 Remix 2 的官方继任者:Remix 团队并入 React Router 之后,同样的 loader/action 路由模型、服务端渲染(SSR)、MDX 路由模块与 Vercel 部署链路被统一收纳进react-router/@react-router/*包体系。本指南以 react-spring 官方文档站(docs/workspace)从 Remix 2.15.2 迁移到 React Router 7 的真实规格为骨架,完整讲解依赖映射、路由约定保留、Vite 插件替换、Vercel 预设切换、Feedback 模块随迁删除,以及一套可复制的端到端验证流程。读完你可以将此方案直接套用到任何 Remix 2 站点迁移中,并知道每一步该改哪些文件、用什么命令验证、守住哪些不变量。

本仓库的specs/003-remix-to-react-router-7/目录保存了这次迁移的完整决策档案(spec.md、research.md、data-model.md、plan.md、quickstart.md、contracts/routes.md),而迁移本身已在仓库中落地:docs/目前运行的正是 React Router 7.15.1。

迁移背景与目标框架选择

为什么是 React Router 7 框架模式

用户的原始诉求很直接:"docs 用的是 Remix 2,应该用 React Router 7,请迁移。"(见 spec.md 的 Input 字段)。技术决策记录在 research.md 的 R1 节:

  • 框架模式而非纯客户端模式:React Router 7 存在两种形态——客户端路由库与带 SSR 的框架模式。框架模式才是 Remix v2 的"直系继任者",保留 loader/action、路由模块、服务端入口约定。若退化为纯客户端库会丢失 SSR,直接违反规格中的 FR-005("每个公开文档路由的首屏必须服务端渲染")与 SC-006(Lighthouse SEO 分数)。
  • 被否决的备选:留在 Remix 2(用户明确要求迁移)、切换 Next.js / Astro / TanStack Start(内容管线需重写,超出框架替换的范围)、客户端-only React Router 7(违反 SSR 需求)。

迁移的边界:只动 docs,不动动画库

react-spring 是一个 pnpm + Turborepo 单体仓库,库层依赖链是rafz → shared → animated → core → targets → umbrella。本次迁移严格限定在docs/workspace 内,plan.md 的 Constitution Check 明确:库包边界(packages/*targets/*demo)零改动,@react-spring/web@react-spring/rafz仍以普通 workspace 依赖被 docs 消费。最终 diff 自检(T058)要求git diff --stat只出现docs/、根pnpm-lock.yaml、根.gitignore、根CLAUDE.md与规格目录。

依赖映射:@remix-run/*全面退场

这是整个迁移的"总开关"。映射表来自># INV-P1:解析树中零 @remix-run/* 包 grep -E '"@remix-run/' pnpm-lock.yaml || echo "OK: no @remix-run packages remain" # INV-P2:整个 workspace 恰好解析一个 major(应为 7) pnpm ls react-router -r --depth -1 --json \ | jq -r '..|.version? // empty' \ | grep -E '^[0-9]+' \ | cut -d. -f1 \ | sort -u # 期望输出:7

落到当前仓库,docs/package.json 已经处于迁移后的目标态:react-router@react-router/node@react-router/serve均为7.15.1,devDependencies 中@react-router/dev@react-router/fs-routes同为7.15.1@remix-run/*@supabase/supabase-js已彻底消失。注意zodcookie被保留——前者被 docs/scripts/docs/frontmatter.ts 使用,后者被theme.server.ts使用,它们并非 Feedback 模块的专属依赖。

路由约定:用@react-router/fs-routes保住 Remix v2 扁平命名

React Router 7 框架模式要求通过app/routes.ts显式声明路由树,但官方提供了@react-router/fs-routes作为 Remix v2 扁平文件命名(flat-route)的迁移离岸垫。这是本次迁移最精妙的一笔(research.md R3):38 个 MDX 路由文件一个都不改名

仓库中 docs/app/routes.ts 的实际内容:

import { flatRoutes } from '@react-router/fs-routes' import type { RouteConfig } from '@react-router/dev/routes' export default flatRoutes({ ignoredRouteFiles: ['**/.*', '**/*.css'], }) satisfies RouteConfig

ignoredRouteFiles原样继承旧 Remix 配置,保证与路由文件同目录的 dotfile 和 CSS Module 不会被误识别为路由。凭借这条规则,docs.components.use-spring.mdx继续解析为/docs/components/use-spring,URL 与线上逐字节一致。被否决的方案包括:改为 RR7 原生嵌套目录约定(要动 39 个文件、破坏已收录的搜索索引 URL、徒增风险)以及手工逐条声明路由(冗长且易漂移)。

这条决策直接服务于规格的核心铁律——URL 契约(contracts/routes.md C1):每个公开路由的 GET 必须返回HTTP 200Content-Type: text/html; charset=utf-8、非空 HTML 正文,且<title>包含预期片段(如/docs/components/use-spring的 title 含useSpring)。完整映射见>import { vercelPreset } from '@vercel/react-router/vite' import type { Config } from '@react-router/dev/config' export default { presets: [vercelPreset()], } satisfies Config

@vercel/react-router是 Vercel 官方为 RR7 框架模式提供的预设,构建时产出 Vercel 运行时期望的部署产物形态(.vercel/output/**),取代旧@vercel/remix的角色。Vercel 项目本身无需手工改配置,靠框架自动识别即可。

类型引用与 TS 配置

research.md R7 给出机械化的类型导入替换表,react-router统一了这些类型:

Remix 旧来源RR7 新来源
import { LoaderFunctionArgs } from '@remix-run/node'import type { LoaderFunctionArgs } from 'react-router'
import { ActionFunctionArgs } from '@remix-run/node'import type { ActionFunctionArgs } from 'react-router'
import { MetaFunction } from '@vercel/remix'import type { MetaFunction } from 'react-router'
import { LinksFunction } from '@vercel/remix'import type { LinksFunction } from 'react-router'
import { json } from '@vercel/remix'删除,改用原生Response.json(...)或 RR7 的data()

仓库中 docs/app/root.tsx 是这一替换的活样板:LinksMetaOutletScriptsScrollRestorationuseLoaderData以及MetaFunctionLinksFunctionLoaderFunctionArgsActionFunctionArgs全部从react-router导入,服务端主题助手getTheme/setTheme仍来自./helpers/theme.serverdocs/tsconfig.jsoninclude加入./.react-router/types/**/*react-router typegen的产物目录),docs/env.d.ts的三斜线引用换成@react-router/node

构建产物路径与 Turborepo 缓存配置

RR7 的默认构建输出与 Remix 2 不同:build/client/(带 hash 的静态资源)+build/server/(服务端 bundle),不再产生 Remix 时代的public/build/。因此 docs/turbo.json 必须同步更新,否则 Turbo 会用过期的 glob 静默误缓存(research.md R9)。仓库当前配置:

{ "extends": ["//"], "tasks": { "build": { "inputs": [ "app/**", "public/**", "react-router.config.ts", "vite.config.mts" ], "outputs": ["build/**", ".react-router/**"] } } }

两点正确性收益:outputsbuild/**一个 glob 同时覆盖 client 与 server;inputs相比旧的["app/**"]补上了public/**(静态资源)与新配置文件,避免缓存失效判断失真。.react-router/**是 typegen 产物,放进outputs让 Turbo 可缓存生成类型。根.gitignore增加.react-router/一行,防止类型生成产物入库。

脚本与开发者工作流(US3)

脚本映射见 research.md R11,仓库 docs/package.json 已处于目标态:

迁移前迁移后
"build": "remix vite:build""build": "react-router build"
"dev": "concurrently \"pnpm dev:remix\" \"pnpm scripts:watch\"""dev": "concurrently \"pnpm dev:rr\" \"pnpm scripts:watch\""
"dev:remix": "vite dev""dev:rr": "react-router dev"
"start": "remix-serve ./build/server/index.js""start": "react-router-serve ./build/server/index.js"
"test:ts": "tsc --noEmit""test:ts": "react-router typegen && tsc --noEmit"

要点:必须用react-router dev(框架模式的官方开发命令,自带 HMR 与路由模块热重载)而不是裸vite devreact-router typegen作为test:ts前置步骤,先按路由生成类型再跑tsc --noEmit,避免冷检出时出现虚假类型错误。根目录pnpm docs:dev/pnpm docs:build通过 Turbo 委托到 workspace,无需改动。唯一改名的是dev:remixdev:rrdocs/README.md与根CLAUDE.md同步更新。

随迁删除:Feedback 模块(FR-019)

规格把"删除页面内 Feedback 模块"与框架迁移捆绑在同一变更中(spec.md FR-019,research.md R13)。理由很务实:不为即将删除的代码支付迁移成本——不必把api.feedback.ts的 Remix 风格 action 签名改写成 RR7 形式,不必验证 Supabase-on-RR7-on-Vercel。删除清单:

  • docs/app/components/Feedback/Feedback.tsxFeedback.css.ts及空目录
  • docs/app/routes/api.feedback.ts
  • docs/app/routes/docs.tsx 中移除 Feedback 的 import 与<Feedback />JSX
  • docs._index.mdx中宣传"每页反馈按钮"的文案改写,保留 GitHub Discussions 链接作为唯一反馈渠道
  • docs/package.json移除@supabase/supabase-js

验证不变量(INV-F1/F2):源码中grep -rE 'Feedback|/api/feedback|@supabase' docs/app docs/scripts应为空;GET /api/feedback在预览环境必须返回 404(由 catch-all splat 路由接管,而非 500)。Supabase 项目与feedback表在数据层原样保留,SUPABASE_URL/SUPABASE_ANON_KEY环境变量的清理是合入后的手动后续项。

完整验证配方:从本地到 Vercel 预览

quickstart.md 提供了一条由贡献者全流程可跑的验证配方,规格里的四条用户故事(US1 框架替换、US2 URL 保持、US3 本地开发体验、US4 Vercel 部署)都落在其中:

1. 干净安装

git checkout 003-remix-to-react-router-7 pnpm install --frozen-lockfile

期望:无 peer-dep 大版本告警;pnpm ls --filter @react-spring/docs | grep remix-run为空。

2. 类型检查pnpm --filter @react-spring/docs test:ts退出码 0(内部即react-router typegen && tsc --noEmit,对应 FR-017 / SC-008)。

3. 开发服务器pnpm docs:dev起在http://localhost:3000,输出应出现react-router dev。逐项检查:首页 SSR 渲染且主题首绘无闪烁(FR-013)、/docs/components/use-spring的 MDX 带代码高亮与 callout、控制台无 hydration 警告、编辑 MDX 约 2 秒内热更新、主题切换硬刷新后保持、DocSearch 搜索可用、内嵌 Sandpack 示例可运行。

4. 生产构建与本地预览pnpm --filter @react-spring/docs build成功后docs/build/client/docs/build/server/存在,且不产生旧的docs/public/build/;随后pnpm --filter @react-spring/docs startreact-router-serve本地提供产物。

5. Vercel 预览部署:推送分支后 Vercel 自动构建出预览 URL,随后对 contracts/routes.md 的整张路由表做 HTTP 状态码巡检:

PREVIEW_URL=https://<your-preview>.vercel.app while read -r path; do code=$(curl -s -o /dev/null -w "%{http_code}" "$PREVIEW_URL$path") echo "$code $path" done < <(awk -F'|' '/^\| `\// {gsub(/^[ `]+|[ `]+$/, "", $2); print $2}' specs/003-remix-to-react-router-7/contracts/routes.md)

所有非 splat 行应打印200;再故意访问一个不存在路径(如/nope)确认 404 页渲染。

6. Lighthouse 抽查:对预览 URL 的//docs/components/use-spring跑 Lighthouse,Performance/SEO/Accessibility 相对线上波动不超过 5 分(SC-006),LCP 在同等网络节流下偏差 10% 以内(SC-007)。

7. 依赖树断言:即上文 INV-P1/INV-P2 两条命令,lockfile 无@remix-run/*react-router仅解析一个 major(7)。

8. 回归与回滚:无数据迁移,回滚即git revert <commit-sha>后重装 lockfile,纯代码与锁文件操作。完成判定还包括:grep -rE '@(remix-run|vercel/remix)' docs/app docs/vite.config.mts docs/env.d.ts为空、docs/app/components/Feedback/目录不复存在(SC-010)。

迁移后的现状核对

对照当前仓库可以确认这次迁移已经完整落地,且与规格高度一致:

  • docs/package.json 依赖与脚本均为 RR7 形态(react-router buildreact-router devreact-router-servereact-router typegen && tsc --noEmit),版本统一为7.15.1
  • docs/vite.config.mts 使用reactRouter(),无installGlobals(),MDX / Vanilla Extract / tsconfigPaths 插件原样保留;
  • docs/app/routes.ts 以flatRoutes()维持 Remix v2 扁平命名,38 个 MDX 路由文件(docs.advanced.*.mdxdocs.components.*.mdx等,见 docs/app/routes 目录)未改名,URL 面与线上逐字节一致;
  • docs/turbo.json 的 inputs/outputs 已按build/**+.react-router/**更新;
  • docs/app/root.tsx 所有运行时与类型导入统一来自react-routertheme.server.ts保持服务端专用;
  • 规格 requirements.md 中 Content Quality、Requirement Completeness、Feature Readiness 三组条目全部勾选——没有遗留[NEEDS CLARIFICATION]标记,成功标准可测量且技术无关,范围显式界定在docs/workspace,Vercel 保持为主机。

从用户故事的验收视角看:US1(现代框架上渲染)由构建、类型检查与本地导航检查覆盖;US2(URL 全量解析)由路由契约巡检覆盖;US3(本地工作流)由dev:rr与 HMR 验证覆盖;US4(Vercel 部署)由预览部署 + 预览巡检覆盖。整个迁移是"小到单分支顺序执行即可、紧耦合到每条故事共享docs/package.jsondocs/app/*"的机械性框架替换——真正决定成败的,是那份将 URL 契约、依赖树不变量与首屏 SSR 行为固化为可执行验证的规格文档。


延伸阅读:想深入这次迁移的每一项决策依据与备选方案分析,可继续阅读 research.md(R1–R13 技术决策)、data-model.md(包映射与路由契约的完整数据模型)、plan.md(59 项任务分 7 个阶段的实施计划)与 tasks.md(含并行任务编排与执行顺序依赖)。

【免费下载链接】react-spring✌️ A spring physics based React animation library项目地址: https://gitcode.com/gh_mirrors/re/react-spring

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询