Epic Stack 路由系统演进:从 remix-flat-routes 迁移到 react-router-auto-routes
2026/9/17 12:33:25 网站建设 项目流程

Epic Stack 路由系统演进:从 remix-flat-routes 迁移到 react-router-auto-routes

【免费下载链接】epic-stackThis is a Full Stack app starter with the foundational things setup and configured for you to hit the ground running on your next EPIC idea.项目地址: https://gitcode.com/GitHub_Trending/ep/epic-stack

本篇文章以 Epic Stack 仓库中的架构决策记录 docs/decisions/045-rr-auto-routes.md 为核心,系统讲解该项目如何将路由清单生成从remix-flat-routes迁移至react-router-auto-routes,包括迁移动机、实际配置方式、文件系统路由约定(路由组、动态参数、嵌套布局、资源路由、共置模块)以及调试与验证手段。读完本文,你将掌握 Epic Stack 当前基于react-router-auto-routes的路由组织规范,能够正确新增页面、嵌套布局与资源路由,并理解这一架构决策对后续升级 React Router 的意义。

背景:为什么放弃 remix-flat-routes

Epic Stack 早期依赖remix-flat-routesapp/routes目录转换为 React Router 在构建期消费的路由清单(route manifest)。该库帮助项目引入了可预测的约定式文件路由,但在长期使用中暴露了一个结构性痛点:

它以+后缀的混合约定将"共置(colocation)"作为默认模式,随着应用规模增长,路由目录结构容易变得不够清晰、难以组织。

与此同时,社区出现了与 React Router 原生约定和 API 对齐的react-router-auto-routes(仓库当前锁定版本为^0.8.4,见 package.json 的 devDependencies)。迁移到它的收益包括:

  • 贴近上游约定:自动路由生成遵循 React Router 官方约定,降低未来升级 React Router 时的破坏风险;
  • 减少自研工具面:需要维护的自定义工具链更少;
  • 迁移路径更清晰:随着 React Router 自身演进,项目可以更平滑地跟进。

决策内容:替换路由清单生成器

根据 docs/decisions/045-rr-auto-routes.md 的 Decision 一节,项目正式决定:

  • react-router-auto-routes替换remix-flat-routes来生成应用路由清单;
  • 新工具纳入构建与开发流水线(react-router buildreact-router dev);
  • remix-flat-routes的配置按新约定迁移。

该决策记录的 Status 为accepted(2025-10-15),意味着这是当前生效的既定方向,而非未定草案。

落地配置:app/routes.ts 与 react-router.config.ts

路由清单入口:app/routes.ts

迁移后的实际配置位于仓库根目录的 app/routes.ts,全文如下:

import { type RouteConfig } from '@react-router/dev/routes' import { autoRoutes } from 'react-router-auto-routes' export default autoRoutes({ ignoredRouteFiles: [ '.*', '**/*.css', '**/*.test.{js,jsx,ts,tsx}', '**/__*.*', // 这是为了让服务端工具函数与路由共置在同一目录下, // 而无需额外创建子目录。如果确实需要包含 "server" 或 // "client" 字样的路由文件名,请使用转义括号写法,例如: // my-route.[server].tsx '**/*.server.*', '**/*.client.*', ], }) satisfies RouteConfig

这段配置值得逐项拆解:

配置项作用
ignoredRouteFiles声明哪些文件不参与路由生成,是 glob 模式数组
'.*'忽略以点开头的隐藏文件(如.DS_Store
'**/*.css'忽略样式文件,防止.css被误当作路由
'**/*.test.{js,jsx,ts,tsx}'忽略单元测试文件,例如仓库中的 app/routes/_auth/auth.$provider/callback.test.ts
'**/__*.*'忽略以双下划线开头的私有模块目录
'**/*.server.*'忽略服务端共置工具,如 app/routes/_auth/login.server.ts
'**/*.client.*'忽略客户端共置工具
satisfies RouteConfig让 TypeScript 校验配置符合@react-router/dev/routes的类型约束

其中'**/*.server.*'/'**/*.client.*'的注释给出了一个重要约定:服务端或客户端专属工具可以与路由文件同目录共置,但要避免在文件名中使用server/client字样;万一路由本身确实需要这些关键词,用方括号转义即可,如my-route.[server].tsx

构建期配置:react-router.config.ts

路由发现行为在 react-router.config.ts 中通过routeDiscovery: { mode: 'initial' }声明,默认开启 SSR(ssr: true)。autoRoutes生成的路由清单正是在此构建配置之下被消费的,两个文件配合共同决定了最终的 URL 结构。

文件系统路由约定:react-router-auto-routes 的命名规则

react-router-auto-routes是对 React Router 原生文件路由约定的增强实现。正如 docs/routing.md 所述,它的核心理念是"鱼与熊掌兼得":

  • 路由与所使用代码共置+前缀的共置模块);
  • 保持有组织的目录结构,让相关路由按需聚合。

以下约定均有仓库真实文件佐证。

路由组(Route Groups):_前缀目录

_开头的目录只用于组织代码,不影响 URL。典型例子:

  • _auth/:认证相关路由(登录、注册、找回密码等),见 app/routes/_auth;
  • _marketing/:营销页面(首页、关于、隐私政策等),见 app/routes/_marketing;
  • _seo/:SEO 路由(sitemap、robots.txt),见 app/routes/_seo。

例如app/routes/_auth/login.tsx对应 URL/login_marketing/about.tsx对应/about

动态参数:$前缀文件与目录

$表示 URL 参数,编译期自动映射为 React Router 的:param

  • $username.tsx/users/:username
  • 目录级参数:app/routes/users/$username/notes/$noteId.tsx对应/users/:username/notes/:noteId,仓库中 app/routes/users/$username 即是真实示例。

参数可以通过 loader 的params读取(类型安全),例如 docs/skills/epic-routing/SKILL.md 中展示的:

// app/routes/users/$username/index.tsx export async function loader({ params }: Route.LoaderArgs) { const username = params.username const user = await prisma.user.findUnique({ where: { username } }) return { user } }

嵌套布局:_layout.tsx

_layout.tsx为子路由提供共享布局,子路由渲染在<Outlet />位置。仓库中的嵌套布局示例:app/routes/settings/profile/_layout.tsx管理个人设置下的所有子页(change-email、connections、passkeys、password、photo、two-factor 等),app/routes/users/$username/notes/_layout.tsx则是笔记模块的二级布局。当同一段路径既需要父级 UI 又需要索引页时,就形成"布局 + index"的标准组合。

资源路由:无 UI 的路由

只导出loaderaction(或两者)、不导出默认组件的文件称为资源路由,用于 API、下载、Webhook 等场景。仓库实例:app/routes/_seo/robots[.]txt.ts 与 app/routes/_seo/sitemap[.]xml.ts 用方括号转义点号,使 URL 中能保留字面量.

共置模块:+前缀

+前缀表示"与路由共置、但不是路由本身"的模块。仓库典型示例是app/routes/users/$username/notes/+shared/目录,其中的 note-editor.tsx 是被新建与编辑笔记两条路由共用的编辑器组件(含表单校验 Schema 与MAX_UPLOAD_SIZE等逻辑)。_marketing/+logos/中的 SVG Logo 资源同理——它们放在路由目录旁却不产生任何 URL。

这与remix-flat-routes时代"+后缀"的默认共置模式恰好相反:新约定下共置是有意为之的特例,普通路由默认是层级化目录,结构更清晰。

查看生成的路由:npx react-router routes

在应用根目录运行以下命令,即可查看基于当前文件结构生成的全部路由(JSX 风格输出),这是熟悉该约定最实用的调试手段:

npx react-router routes

app/routes 的完整目录树(72 个文件、17 个目录)会生成一份对应的<Routes>树,节选关键片段:

<Routes> <Route file="root.tsx"> <Route path="*" file="routes/$.tsx" /> <Route path="auth/:provider/callback" file="routes/_auth/auth.$provider/callback.ts" /> <Route path="auth/:provider" index file="routes/_auth/auth.$provider/index.ts" /> <Route path="login" file="routes/_auth/login.tsx" /> <Route path="onboarding" index file="routes/_auth/onboarding/index.tsx" /> <Route path="robots.txt" file="routes/_seo/robots[.]txt.ts" /> <Route path="sitemap.xml" file="routes/_seo/sitemap[.]xml.ts" /> <Route path="settings/profile" file="routes/settings/profile/_layout.tsx"> <Route path="change-email" file="routes/settings/profile/change-email.tsx" /> <Route index file="routes/settings/profile/index.tsx" /> <Route path="two-factor" file="routes/settings/profile/two-factor/_layout.tsx"> <Route index file="routes/settings/profile/two-factor/index.tsx" /> <Route path="verify" file="routes/settings/profile/two-factor/verify.tsx" /> </Route> </Route> <Route path="users/:username" index file="routes/users/$username/index.tsx" /> <Route path="users/:username/notes" file="routes/users/$username/notes/_layout.tsx"> <Route path=":noteId" file="routes/users/$username/notes/$noteId.tsx" /> <Route path=":noteId/edit" file="routes/users/$username/notes/$noteId_.edit.tsx" /> <Route index file="routes/users/$username/notes/index.tsx" /> <Route path="new" file="routes/users/$username/notes/new.tsx" /> </Route> <Route path="users" index file="routes/users/index.tsx" /> </Route> </Routes>

从这段输出可以印证多个约定细节:

  • _auth_marketing_seo路由组不出现在路径中;
  • auth.$provider/callback.ts中的$provider被编译为:provider
  • robots[.]txt.ts的转义点号保真为robots.txt
  • $noteId_.edit.tsx通过$noteId_语法表示"参数段后紧跟静态段edit";
  • _layout.tsx生成嵌套<Route>,子路由挂在其下;
  • 资源路由(robots、sitemap 等)与普通路由在清单中无本质区别,仅文件内容不导出组件。

编写约定速查

结合 docs/skills/epic-routing/SKILL.md 与仓库实际结构,新增路由时应遵循:

需求文件名 / 目录示例
普通页面about.tsxapp/routes/_marketing/about.tsx
段索引页index.tsxapp/routes/users/index.tsx
动态参数$param.tsxapp/routes/users/$username/index.tsx
嵌套布局_layout.tsxapp/routes/settings/profile/_layout.tsx
参数后接静态段$param_.action.tsxapp/routes/users/$username/notes/$noteId_.edit.tsx
带点号的资源路由name[.]ext.tsapp/routes/_seo/robots[.]txt.ts
共置工具模块+shared/*.server.*app/routes/users/$username/notes/+shared/note-editor.tsx

同时需要注意:

  • 布局组件必须渲染<Outlet />,否则子路由不会显示;
  • 资源路由不要导出默认组件;
  • 参数使用$param写法,而非:param[param]
  • 路由组(_目录)不会出现在 URL 中;
  • 遵循"尽量简单"原则,不要在真正需要之前就堆砌嵌套结构。

迁移的后果与验证

决策记录列出了三项核心后果:

  1. 升级兼容性提升:自动路由生成遵循 React Router 约定,未来升级 React Router 时破坏风险降低;
  2. 文档与示例同步更新:贡献者需要遵循react-router-auto-routes的命名与组织规则,仓库因此更新了 docs/routing.md 与 docs/skills/epic-routing/SKILL.md 以反映新的目录语义;
  3. 全量验证通过:所有既有功能经npm run validate验证,在新路由系统下工作正常。

npm run validate(定义于 package.json)实际串联了test --runlinttypechecktest:e2e:run四条流水线,其中typecheck会先执行react-router typegen生成类型,再运行tsc——这保证了路由清单的类型信息与源码始终一致。而 tests/e2e 下的 Playwright 用例(如 notes.test.ts、onboarding.test.ts)则从端到端层面覆盖了这些路由的真实可访问性。

总结

remix-flat-routesreact-router-auto-routes,Epic Stack 完成了一次"向 React Router 原生约定靠拢"的架构收敛:_前缀组织路由组、$声明动态参数、_layout.tsx构建嵌套布局、+前缀支持共置模块,配合app/routes.ts中细粒度的ignoredRouteFiles白名单与npx react-router routes的可视化输出,路由系统的可维护性与可迁移性都得到了实质提升。对使用该模板启动新项目的开发者而言,掌握这套约定,就等于掌握了整个应用页面与接口的"地图"。

【免费下载链接】epic-stackThis is a Full Stack app starter with the foundational things setup and configured for you to hit the ground running on your next EPIC idea.项目地址: https://gitcode.com/GitHub_Trending/ep/epic-stack

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

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

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

立即咨询