资讯动态

Next.js App Router 路由组实战:布局拆分与目录组织

发布时间:2026/10/17 6:56:11 来源:尧图企业网站定制
1. 为什么要关注路由组先说个我自己的经历。早期做 Next.js 项目的时候代码组织基本靠简历页面多了就往app目录下面堆。堆到几十个页面时问题开始冒出来了——app目录里既有前台页面又有后台管理既有带布局的页面又有不带布局的弹窗页光看文件名根本分不清谁是谁。同事接手的时候光梳理目录结构就花了两天。后来我读 Next.js 官方文档看到 route groups 这个概念当时不理解它到底解决什么问题。直到一次重构我把一个混合型项目按路由组彻底整理了一遍才真正体会到它的价值。简单说路由组就是一对括号包起来的目录比如(marketing)、(dashboard)、(auth)。它能把功能相近的路由聚在一起同时不影响到 URL 路径。为什么这个东西重要因为 Next.js 的 App Router 把很多事情都绑在了文件夹和文件约定上layout 是共享布局page 是对应页面loading 是加载态。如果没有任何分组手段全局 layout 只能有一个页面之间的共享能力只能靠重复代码。路由组相当于给了你一套逻辑分组机制把组织结构从 URL 里彻底剥离出来。从实际效果看我用路由组整理完项目后目录的语义清晰了找文件快了很多共享 layout 的代码量也大幅减少。这篇文章不讲空泛概念直接聚焦在工程实践上路由组能做什么、怎么用、有哪些坑、实际项目里怎么组织代码。适合正在用或打算用 App Router 的开发者参考。2. 路由组的核心用法2.1 路由组到底是什么先解释下机制。在 App Router 里凡是带圆括号的目录从 URL 结构中完全消失。比如app/ (shop)/ products/ page.tsx cart/ page.tsx对应的 URL 是/products和/cart不是/(shop)/products。括号只影响文件组织不影响路由路径。关键点在于它能承载 layout。每个路由组目录里可以放自己的layout.tsx这样该组下的所有页面自动包裹对应布局app/ (shop)/ layout.tsx products/ page.tsx cart/ page.tsx如果我们再有一个(blog)组就能拥有完全独立的布局而且这些布局本身可以继续嵌套。这意味着你可以在同一个 Next.js 应用里为完全不同的业务模块配置不同的布局体系而不必在一个全局 layout 里写一大堆条件判断。还有一个常见场景同一个页面在不同布局下的访问路径不变。比如移动端和桌面端想要不同布局但 URL 保持/settings路由组就派上用场了。具体做法是把settings/page.tsx拆分成(mobile)/settings/page.tsx和(desktop)/settings/page.tsx通过 user agent 或设备信息做内部重定向URL 始终是/settings。2.2 共享布局与独立布局的拆分策略日常开发里最爽的一个点就是原来需要在全局 layout 里写if (pathname.startsWith(/dashboard))这种路由判断现在直接通过目录结构解决。项目里我是这么分的app/ (site)/ layout.tsx // 官网头部底部 page.tsx // 首页 about/ page.tsx (dashboard)/ layout.tsx // 侧边栏 顶栏 dashboard/ page.tsx projects/ page.tsx (auth)/ layout.tsx // 居中卡片式布局 login/ page.tsx register/ page.tsx这里(site)对应官网展示(dashboard)对应登录后的管理后台(auth)对应登录注册页。三组各有独立的 layout彼此互不干扰。访问/时走(site)/layout.tsx访问/dashboard时走(dashboard)/layout.tsx虽然页面上是一个应用但布局系统是完全隔离的。这背后有个很核心的机制Next.js 会把每个路由段的 layout 向上聚合最终形成一颗 layout 树。有了路由组我们可以很轻松地把多套界面体系装进同一个应用里不用硬造子域名也不用维护多套部署。2.3 不改变 URL 的优点最好玩的一个应用是在 A/B 测试里。同一个页面结构我想让不同用户看到不同的实验版本但 URL 不能变这么做app/ (variantA)/ home/ page.tsx (variantB)/ home/ page.tsx两个版本共同指向/home。在服务端根据实验标记返回其中一个版本的页面用户无感知URL 统一。类似的场景还包括不同语言的站点共享一个路由路径但需要不同的布局不同入口的落地页需要完全不同的页面设计但 URL 层级不能出现额外前缀。这类需求在以前要借助中间件重写 URL 才能完成现在用路由组就能直接在目录层面划分。3. 与页面布局体系的配合3.1 全局布局如何自然过渡到局部布局很多项目的原始结构是只有根layout.tsx。页面一多全局 layout 就会变成一个大杂烩头部、侧边栏、页脚统统堆在里面再通过路由判断隐藏显示。这种做法不能说不能用但维护成本肉眼可见的膨胀。路由组天然就是解决这个问题的。我一般的做法是根 layout 只保留全站基础内容比如字体、全局样式、Provider 注入。每个真实业务区域在路由组内配置自己的 layout。如果某个页面不需要这套布局就放在组外或者新建一个专门的组。举例来说整个站需要 Redux Provider 和主题注入那就放进根 layout。而 dashboard 的侧边栏、顶部用户信息、页面切换过渡动画这些都放进(dashboard)/layout.tsx。组件职责边界非常清楚。3.2 嵌套路由组的组织方式复杂项目里单层路由组不够用。比如企业内部系统可能由多个子系统组成每个子系统又有自己独立的导航结构。这时可以把路由组嵌套起来app/ (dashboard)/ (overview)/ layout.tsx page.tsx (analytics)/ layout.tsx reports/ page.tsx(overview)的 layout 可以提供总览统计卡片所需的边栏(analytics)的 layout 则可以提供专用的报表时间范围筛选器两者都挂在(dashboard)这个大组下。有个细节要注意路由组虽然不产生 URL 段但它确实是路径树的一环。所以嵌套路由组的 layout 层级必须符合你想要的实际包装关系。比如(dashboard)/layout.tsx作为最外层提供应用框架(analytics)/layout.tsx作为内层提供报表专属功能。访问/reports时两者会叠加符合直觉。这种嵌套能力在多个项目里实测非常好用。写代码时每个目录内聚度高想加新功能直接看目录就知道 layout 和路由该怎么放。3.3 路由组与并行路由的配合并行路由是 App Router 的另一个特性目录名用开头可以在同一个 layout 里渲染多个区块。路由组和并行路由配合时有两个注意点第一路由组不能直接作为并行路由的 slot 目录。app/modal/(auth)/login/page.tsx这种方式会失效因为modal本身已经代表了 slot 的划分内部再用路由组反而会造成混乱。第二并行路由 slot 之间需要完全独立的 layout 树这时在slot目录内使用路由组来细分不同页面是可行的app/ modal/ (auth)/ login/ page.tsx (info)/ tips/ page.tsx page.tsx这样modal这个 slot 内部(auth)和(info)两个分组可以各自定义内部结构。我在做复杂弹窗系统的时候用这套方案把每个弹窗类型对应的页面拆到独立分组里维护起来非常舒服。4. 路由组在真实项目里的组织方案4.1 应用示例按功能模块组织路由组下面用我近期重构的一个实际项目骨架来说明。这是一个集官网、用户后台、管理后台于一体的项目最开始结构非常混乱重构后分层如下app/ layout.tsx // 全局字体 Provider 错误边界 (marketing)/ layout.tsx // 官网布局导航栏 页脚 page.tsx // 官网首页 pricing/ page.tsx blog/ page.tsx (app)/ layout.tsx // 登录后布局顶栏 侧边栏 dashboard/ page.tsx invoices/ page.tsx (auth)/ layout.tsx // 认证布局居中卡片 login/ page.tsx register/ page.tsx (admin)/ layout.tsx // 管理后台布局 admin/ users/ page.tsx settings/ page.tsx这套结构的好处非常直观pricing、blog和dashboard同在一个应用里但走的是完全不同的布局。新增一个官网页面直接丢进(marketing)/下不需要关心其他模块。用户后台和管理后台在访问路径上都有各自的前缀/dashboard、/admin但组织上通过命名分区以后再进一步做代码拆分也不难。4.2 组织时需要注意的命名与目录原则路由组命名本身不影响 URL但会影响代码可读性。命名上我总结了几条原则用业务含义不用实现细节。比如(marketing)、(auth)不要用(withHeader)、(withSidebar)这种名字。前者读代码时直接理解业务分区后者只会让人困惑。一个组不要塞过多的页面。如果一个组下页面超过 20 个说明分区粒度太粗需要进一步拆分。顶层组数量控制在 5 个以内。超过这个数量根目录就变得难以浏览。路由组内不要放路由本身的顶层语义目录比如不要出现(marketing)/pages/这种多余层级。另外特别要提醒路由组名字在嵌套层级里必须是唯一的。同一个路由深度下如果有两个(main)那会在开发时直接报错因为解析器无法区分它们。即便不报错的路由组重复也会让代码审阅变得非常困难。4.3 组内共享代码的放置位置经常有人问components/、lib/这类共享代码应该放在app里面还是外面。我的建议是真正全站共享的基础组件、工具函数、类型定义放到app之外的顶层目录比如项目根下的src/components、src/lib。与某个路由组强相关的组件比如只有(dashboard)使用的侧边导航菜单、统计卡片可以就近放在(dashboard)/_components或(dashboard)/components下。这里有个细节Next.js 默认会把page.tsx、layout.tsx之外的文件视为路由可能接入的资源。为避免 Next.js 把不必要的目录纳入路由解析路由组内的非路由文件最好加上下划线前缀这样 Next.js 会明确跳过。比如app/ (dashboard)/ _components/ Sidebar.tsx _lib/ charts.ts dashboard/ page.tsx实测下来这个方式既不污染路由也保持了目录的内聚。如果你用 TypeScript 路径别名也方便直接引到具体位置不会导致 IDE 解析混乱。5. 常见问题与排查技巧实录5.1 多个根布局的冲突问题每个 Next.js 应用只能存在一个根app/layout.tsx路由组不能改变这一点。原因在于根 layout 需要渲染html和body如果有两个根 layout页面初始化时就会出现 body 嵌套的严重错误。有些人希望给官网和后台分别用不同根布局比如分别管理各自的 meta 和 SEO试图通过路由组去实现两个根 layout。实测这条路走不通。我能给的建议是如果只是想让不同部分有不同的头部信息root layout 放公共部分其他差异化内容放各组 layout效果完全够用。如果真的要彻底分离两端应用请直接拆成两个 Next.js 应用或者在应用入口做条件渲染而不是在 App Router 结构层面强行搞两个根 layout。app/(site)/layout.tsx和app/(admin)/layout.tsx覆盖不同的子路由它们不是根 layout不会出现html叠加的问题。这点理解清楚后很多报错就能避免。5.2 路由组与动态路由组合出现的问题在你的路由组内部使用动态路由时要注意路径段的变化。比如app/ (shop)/ products/ [slug]/ page.tsx访问/products/abcparams.slug为abc。开发时很容易混淆的一点是路由组产生的目录层级不会出现在params中。比如app/(shop)/products/[slug]/page.tsx里如果试图在代码中依赖params.shop那是得不到值的因为路由组没有产生路由段。这其实是个非常隐蔽的坑。我见过有人写这种代码导致在服务端渲染时拿不到想要的参数排查半天才发现是路由组的层级被忽略了。所以建议路由组内做动态路由参数读取时始终从params里取实际 URL 段的名称路由组的名字不要作为参数依赖。5.3 中使用中间件配合路由组的建议路由组的原理决定了它是一个开发期和组织期的概念不是运行时 URL 概念。中间件执行的阶段是在路由匹配之后但它能看到的是 URL而不是路由组。比如中间件里用request.nextUrl.pathname判断路径时拿到的是/dashboard而不会看见(app)。有一个场景需要注意如果想用中间件里的逻辑决定把某个请求导向哪个路由组下的页面你不能直接写/app/dashboard而要直接写/dashboard。中间件和后端服务只能理解显式 URL 路径。这个我踩过坑写过new URL(/(app)/dashboard, request.url)结果自然是 404。在中间件做权限校验时用 URL 路径匹配业务前缀即可。也就是说你在组织上可以把你想要保护的页面放在(protected)分组然后在中间件里匹配/dashboard、/admin的实际路径前缀。职责分配为路由组负责组织页面中间件负责拦截请求。5.4 路由组中布局互相影响的问题有一种很容易踩的坑是多个路由组共享某个静态变量时布局意外地互相污染。我们说路由组不改变 URL但 Next.js 的渲染树结构是包含路由组层级的。如果某个布局组件或页面组件在模块顶层声明了一个可变变量那么这个变量是模块级的会被多个路由组共享。实际的体现是(dashboard)的 layout 里定义了一个数组或对象在服务端渲染时不断往里 push 数据。由于模块缓存的存在另一个组(admin)的 layout 可能会读到同一个数组。这个问题和 Next.js 的模块缓存机制有关不是路由组本身造成但在路由组多的项目里更容易被触发。规避建议任何布局和页面内的可变状态都放到服务端组件边界内或者用 React 的状态机制管理不要依赖模块级变量。路由组方便了组织但也意味着同一模块可能被更多入口复用状态污染的概率在变大。5.5 开发时的 HMR 问题还有一个经常被忽略的问题路由组改动名称后旧路径不会自动 301 跳转。比如把(oldgroup)/page.tsx挪到(newgroup)/page.tsxURL 可能没变但热更新后文件指纹、模块路径会变。如果项目里有用到基于文件路径的缓存要留意清理。实测中发现在某些 Next.js 版本中路由组删改后残留的.next缓存可能触发“page not found”或本地开发环境白屏。这种情况优先执行rm -rf .next再重启基本都能恢复。看起来像什么代码问题其实就是缓存问题。5.6 常见问题速查表问题现象可能原因应对方式根目录下有多个layout.tsx报错多个根布局冲突保留一个根app/layout.tsx其余放进路由组URL 多出一层/(...)把路由组当成普通目录访问确认路由组带括号路径按不含括号的 URL 访问动态路由读不到某些参数误把路由组名当成路由段从params中读取实际 URL 段名称中间件重定向 404重定向 URL 写了路由组路径使用显式 URL 路径不带括号热更新后白屏/page not found缓存残留删除.next并重启路由组之间状态互相污染模块级可变变量被共享改用 React 内部状态或每次重新创建6. 适合路由组的场景与不适合路由组的场景6.1 适合的业务场景从几个真实项目里总结出的规律是路由组适合以下场景官网 应用混合型项目marketing 页面与应用后台同时部署在一个 Next.js 应用里共享部署资源和域名但布局差异大。多级后台系统不同类型的管理页面有各自专属操作布局需要视觉隔离。多入口落地页同一功能面向不同用户群时提供不同 UI而 URL 保持一致。A/B 测试与实验页面同一路径下据实验标记渲染不同版本URL 不变。6.2 不适合的业务场景也要说实话路由组不是万能方案。有些场景用了反而增加复杂度需要完全隔离的部署单元比如某两个模块功能和团队都完全独立。这时候建议拆成独立应用用独立部署流程。强行合并到一个 Next.js 应用里发布和回滚都会互相牵连。URL 本身就需要体现层级区分比如产品详情页天然就要/products/[id]没必要为了“组织代码”而刻意把产品相关页面抽到路由组里如果业务上这个前缀是有语义的。团队对 App Router 还不熟悉如果团队里大部分人不理解 route group 的含义强行使用会在代码 review 时产生大量争议。这种情况可以先用目录和注释规范等团队熟悉后再推行。6.3 与普通目录混合组织的取舍不是说要全项目全用路由组。合理的做法是顶层用路由组做大的业务分区二级目录用普通目录表达 URL 层级两者结合。举个例子app/ (app)/ dashboard/ page.tsx projects/ page.tsx settings/ page.tsx (marketing)/ page.tsx这里(app)定义了登录后的总体布局而dashboard、projects、settings作为普通目录组成了 URL 层级。相比全路由组结构这种混合方式语义更清晰因为不是每个模块都需要独立的布局体系。实际经验告诉我把路由组当作“布局作用域”去用而不是“目录作用域”去用设计起来会顺畅很多。如果只是为了文件归类而建一个路由组那可能反而让项目更复杂。7. 基于路由组的工程规范建议7.1 规范路由组的命名习惯命名问题看似小事但影响长远。我推荐统一使用能代表业务模块的英文单词或业务缩写避免随意命名。建议列一个简单规范(site)/(marketing)官网包含公开页面(app)/(dashboard)登录后系统主区(auth)登录注册(admin)管理后台(internal)内部工具(experimental)实验性页面这些规范可以在团队文档里固定下来新接手的人看到目录名就知道是什么业务分区不用逐个打开确认。7.2 页面与布局的目录内聚规则每个业务区块内建议做一个统一约定路由页面文件只放page.tsxlayout 文件只放layout.tsx组件都收在_components下。这样每个路由组文件夹内部结构一致新成员很容易找到入口和共享组件。我习惯为每个路由组内部建一个局部入口文件比如(dashboard)/_components/index.ts统一导出该区块通用组件、工具。这样引用路径也短重构时只需移动一个文件夹即可。7.3 路由组大小与拆分原则当单个路由组过大时会变得不好维护。我的拆分参考是页面数量 10 个以内单组页面数量 10~30按功能进一步拆分成子路由组超过 30考虑拆应用或拆微前端模块这不是硬指标而是基于维护体验的经验值。比如后台系统(app)下如果既有 dashboard又有项目管理、用户管理、财务报表不要全部平铺在一个(app)下而是拆成(dashboard)、(projects)、(reports)等组每个组内再配置自己的布局。每个组的文件数量相对均匀检索和定位都会更轻松。8. 结语与实践心得用路由组组织代码一年多我最深的一点体会是它最核心的价值不是“让目录好看”而是让布局系统和路由系统解耦。以前没有路由组时我习惯把业务模块做成子路径然后为了给每个模块定制布局不得不在全局做一个渲染入口不停地判断当前路径。这个入口文件日积月累会很臃肿。有了路由组后每个模块的布局变成了目录结构的一部分职责非常明确。我只需要在组内添加 layout页面自然共享对应布局任何后来者看目录结构就能理解模块的边界。再分享一个细节把路由组之后跟loading.tsx和error.tsx配合使用会得到非常细粒度的页面反馈。例如(dashboard)/loading.tsx只会在 dashboard 相关页面加载时出现而不会影响官网页面的加载体验。这种按模块控制边界的能力很快会成为团队协作的共识远离“全局加载态拖垮所有页面体验”的老问题。这里还有一个小技巧路由组通常搭配路径别名管理。在根tsconfig.json里为路由组配置别名比如app指向(app)、marketing指向(marketing)引用依赖时就不用写长串相对路径代码也更清爽。最后说一句路由组不是银弹。用之前先想清楚业务边界用的时候先定好命名与目录规范效果会事倍功半。多试几次你会慢慢找到适合自己团队的路由组织方式。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑