Next.js 目录结构与路由体系完整学习指南下面从「整体架构 → 目录约定 → 路由分类 → 特殊文件」系统讲一遍一、先建立全局认知两套路由系统Next.js 历史上存在两套路由体系目录状态特点Pages Routerpages/仍支持偏遗留文件即路由getServerSideProps 等App Routerapp/当前主流React Server Components、布局嵌套、更细粒度控制学习建议以 app/ 为主了解 pages/ 即可读老项目时会遇到。项目根目录典型结构├── src/ # 可选但推荐 │ ├── app/ # ★ 路由核心App Router │ ├── components/ # 可复用 UI 组件不参与路由 │ ├── lib/ # 工具函数、数据访问层 │ ├── hooks/ # 自定义 React Hooks │ ├── types/ # TypeScript 类型 │ └── styles/ # 全局样式可选 ├── public/ # 静态资源/logo.png → 直接访问 ├── next.config.ts # Next.js 配置 ├── middleware.ts # 边缘中间件鉴权、重定向等 └── package.jsonsrc/ 目录把应用代码收进 src/根目录留给配置文件更清晰。有无 src/ 功能一样只是 app/ 变成 src/app/。二、App Router 核心思想文件夹 路由段Segmentpage.tsx 可访问的页面端点app/ ├── layout.tsx → 根布局包裹所有页面 ├── page.tsx → 路由/ ├── about/ │ └── page.tsx → 路由/about └── blog/ ├── layout.tsx → 仅包裹 /blog/\* 的布局 ├── page.tsx → 路由/blog └── [slug]/ └── page.tsx → 路由/blog/hello-world关键规则只有包含 page.tsx或 route.ts的文件夹才会成为公开 URL其他文件layout.tsx、loading.tsx、components/ 子目录不会变成路由路由是嵌套的父 layout 包裹子页面举一个项目里的例子src/app/ ├── layout.tsx # 根布局html、body、全局 Header ├── (main)/ # 路由组URL 中不出现 (main) │ ├── layout.tsx # 主站布局footer │ ├── page.tsx # / │ ├── posts/[slug]/ # /posts/:slug │ └── tags/[slug]/ # /tags/:slug └── about/page.tsx # /about若在根 app 下三、路由分类重点1. 静态路由Static Routes文件夹名直接映射 URLapp/dashboard/settings/page.tsx → /dashboard/settings最简单适合固定页面关于我们、设置页、404 落地页等。2. 动态路由Dynamic Segments用方括号 [] 表示动态参数app/posts/[slug]/page.tsx → /posts/hello /posts/my-first-post页面里通过 params 读取// Next.js 15params 是 Promise需要 await export default async function Page({ params, }: { params: Promise{ slug: string }; }) { const { slug } await params; return h1{slug}/h1; }比如 posts/[slug]/page.tsx、tags/[slug]/page.tsx 就是这类。常见动态参数名[id] → 数字 ID[slug] → 语义化别名[…slug] → 见下文「捕获所有」3. 捕获所有路由Catch-allapp/docs/[...slug]/page.tsxURLparams.slug/docs/a[‘a’]/docs/a/b/c[‘a’, ‘b’, ‘c’]适合文档站、多级分类、文件路径式 URL。4. 可选捕获所有路由Optional Catch-allapp/shop/[[...slug]]/page.tsx与上类似但根路径也匹配URLparams.slug/shopundefined/shop/electronics[‘electronics’]一个 page.tsx 同时处理列表页和详情页时很实用。5. 路由组Route Groups文件夹名用 (名称) 包裹不出现在 URL 中只用于组织代码、共享布局app/ ├── (marketing)/ │ ├── layout.tsx # 营销站布局大 Banner、无侧边栏 │ ├── page.tsx # / │ └── pricing/page.tsx # /pricing └── (dashboard)/ ├── layout.tsx # 后台布局侧边栏 └── settings/page.tsx # /settings路由组把主站页面聚在一起共用 footer 布局URL 仍是 /、/posts/xxx不会出现 /marketing/…。典型用途多套布局官网 vs 后台按业务模块分文件夹URL 保持扁平 6. 平行路由Parallel Routes用 文件夹名 定义同一布局下的多个独立插槽app/ └── dashboard/ ├── layout.tsx ├── analytics/page.tsx ├── team/page.tsx └── page.tsx// dashboard/layout.tsx export default function Layout({ children, analytics, team, }: { children: React.ReactNode; analytics: React.ReactNode; team: React.ReactNode; }) { return ( {children} {analytics} {team} / ); }适合仪表盘多面板、模态 主内容同时渲染、按条件显示不同区域。学习阶段知道概念即可实战多在复杂后台。7. 拦截路由Intercepting Routes用 (.)、(…)、(…)(…) 等前缀在同一布局内「拦截」导航常用于模态框app/ ├── feed/ │ └── page.tsx ├── photo/ │ └── [id]/page.tsx # 直接访问 /photo/1 → 完整页 └── feed/ └── modal/ └── (.)photo/[id]/page.tsx # 从 /feed 点进 → 弹窗URL 仍是 /photo/1从列表点进详情是弹窗刷新或直接打开 URL 仍是完整页面。Instagram、Twitter 那种「点图弹层」常靠这个实现。8. 私有文件夹Private Folders以下划线 _ 开头的文件夹不会被路由系统识别app/ ├── \_components/ # 仅本 app 下用的组件 ├── \_lib/ # 路由相关工具 └── blog/ └── page.tsx和放在 src/components/ 类似只是把代码就近放在路由旁边Colocation。9. API / 后端路由Route Handlersroute.ts不是 page.tsx定义 HTTP 接口app/api/users/route.tsexportasyncfunctionGET(){returnResponse.json({users:[]});}exportasyncfunctionPOST(request:Request){constbodyawaitrequest.json();returnResponse.json({ok:true});}→ GET/POST /api/users动态 APIapp/api/posts/[id]/route.ts → /api/posts/123对比 Pages Router以前是 pages/api/users.ts现在统一在 app/api/ 下用 route.ts。四、app/ 下的特殊文件约定式文件这些文件名有固定语义Next.js 自动识别文件作用layout.tsx共享 UI可嵌套保留状态page.tsx页面 UI路由可访问loading.tsx加载态Suspense 边界error.tsx错误边界必须是 Client Componentnot-found.tsx404 UI配合 notFound()global-error.tsx根级错误连 layout 都挂了template.tsx类似 layout但每次导航重新挂载default.tsx平行路由的 fallbackroute.tsAPI Route Handleropengraph-image.tsx动态 OG 图sitemap.ts / robots.tsSEOicon.tsx / favicon.ico站点图标嵌套关系示意app/layout.tsx (main)/layout.tsx Suspense 错误时 notFound posts/loading.tsx posts/[slug posts/[slug posts/[slugloading.tsx该段及子路由加载时显示error.tsx该段运行时错误时显示需 ‘use client’not-found.tsx调用 notFound() 或没有匹配动态数据时显示五、布局Layout与模板Template的区别layout.tsxtemplate.tsx导航时保持状态不重新挂载每次导航重新挂载典型用途侧边栏、导航栏入场动画、每次刷新表单大多数场景用 layout 即可。六、Server vs Client 组件与路由App Router 默认是 Server Component// 默认在服务端执行可直接 async 查数据库 export default async function Page() { const data await fetch(...); return div.../div; }需要交互时加 ‘use client’use client; export default function Counter() { const [n, setN] useState(0); ... }与路由的关系page.tsx、layout.tsx 可以是 Server Componenterror.tsx、部分需要 hooks 的 UI 通常是 Client Component推荐页面用 Server交互拆成小 Client 组件七、数据获取与渲染策略和路由强相关方式说明默认 fetchServer Component 里 fetch自动 dedupeexport const dynamic ‘force-static’强制静态export const revalidate 60ISR60 秒重新验证generateStaticParams构建时预生成动态路由export const dynamic ‘force-dynamic’每次请求渲染动态路由静态化示例exportasyncfunctiongenerateStaticParams(){constpostsawaitgetPosts();returnposts.map((p)({slug:p.slug}));}若项目没写 generateStaticParams构建时动态路由会按配置走 SSR 或按需生成。八、根目录其他重要文件middleware.ts项目根或 src/ 下在请求到达页面之前执行Edge Runtimeimport{NextResponse}fromnext/server;importtype{NextRequest}fromnext/server;exportfunctionmiddleware(request:NextRequest){if(!request.cookies.get(token)){returnNextResponse.redirect(newURL(/login,request.url));}}exportconstconfig{matcher:[/dashboard/:path*],};用途鉴权、国际化前缀、A/B、重写 URL。public/public/logo.svg → 浏览器访问 /logo.svg不经 webpack 处理。next.config.ts重定向、图片域名、实验特性、环境变量透传等。九、Pages Router 速览对比学习若看到 pages/ 目录规则不同pages/ ├── index.tsx → / ├── about.tsx → /about ├── posts/[slug].tsx → /posts/:slug └── api/users.ts → /api/users无 layout.tsx 嵌套用 _app.tsx、_document.tsx数据getStaticProps、getServerSideProps、getStaticPaths新项目不建议再用但读文档、维护老库时会遇到十、推荐的学习路线由浅入深静态路由 page.tsxlayout 嵌套动态路由 [slugloading / error / not-found路由组 (group)Route Handlers apimiddleware平行路由 / 拦截路由generateStaticParams / ISR
Next.js - 02 - 目录结构与路由体系
Next.js 目录结构与路由体系完整学习指南下面从「整体架构 → 目录约定 → 路由分类 → 特殊文件」系统讲一遍一、先建立全局认知两套路由系统Next.js 历史上存在两套路由体系目录状态特点Pages Routerpages/仍支持偏遗留文件即路由getServerSideProps 等App Routerapp/当前主流React Server Components、布局嵌套、更细粒度控制学习建议以 app/ 为主了解 pages/ 即可读老项目时会遇到。项目根目录典型结构├── src/ # 可选但推荐 │ ├── app/ # ★ 路由核心App Router │ ├── components/ # 可复用 UI 组件不参与路由 │ ├── lib/ # 工具函数、数据访问层 │ ├── hooks/ # 自定义 React Hooks │ ├── types/ # TypeScript 类型 │ └── styles/ # 全局样式可选 ├── public/ # 静态资源/logo.png → 直接访问 ├── next.config.ts # Next.js 配置 ├── middleware.ts # 边缘中间件鉴权、重定向等 └── package.jsonsrc/ 目录把应用代码收进 src/根目录留给配置文件更清晰。有无 src/ 功能一样只是 app/ 变成 src/app/。二、App Router 核心思想文件夹 路由段Segmentpage.tsx 可访问的页面端点app/ ├── layout.tsx → 根布局包裹所有页面 ├── page.tsx → 路由/ ├── about/ │ └── page.tsx → 路由/about └── blog/ ├── layout.tsx → 仅包裹 /blog/\* 的布局 ├── page.tsx → 路由/blog └── [slug]/ └── page.tsx → 路由/blog/hello-world关键规则只有包含 page.tsx或 route.ts的文件夹才会成为公开 URL其他文件layout.tsx、loading.tsx、components/ 子目录不会变成路由路由是嵌套的父 layout 包裹子页面举一个项目里的例子src/app/ ├── layout.tsx # 根布局html、body、全局 Header ├── (main)/ # 路由组URL 中不出现 (main) │ ├── layout.tsx # 主站布局footer │ ├── page.tsx # / │ ├── posts/[slug]/ # /posts/:slug │ └── tags/[slug]/ # /tags/:slug └── about/page.tsx # /about若在根 app 下三、路由分类重点1. 静态路由Static Routes文件夹名直接映射 URLapp/dashboard/settings/page.tsx → /dashboard/settings最简单适合固定页面关于我们、设置页、404 落地页等。2. 动态路由Dynamic Segments用方括号 [] 表示动态参数app/posts/[slug]/page.tsx → /posts/hello /posts/my-first-post页面里通过 params 读取// Next.js 15params 是 Promise需要 await export default async function Page({ params, }: { params: Promise{ slug: string }; }) { const { slug } await params; return h1{slug}/h1; }比如 posts/[slug]/page.tsx、tags/[slug]/page.tsx 就是这类。常见动态参数名[id] → 数字 ID[slug] → 语义化别名[…slug] → 见下文「捕获所有」3. 捕获所有路由Catch-allapp/docs/[...slug]/page.tsxURLparams.slug/docs/a[‘a’]/docs/a/b/c[‘a’, ‘b’, ‘c’]适合文档站、多级分类、文件路径式 URL。4. 可选捕获所有路由Optional Catch-allapp/shop/[[...slug]]/page.tsx与上类似但根路径也匹配URLparams.slug/shopundefined/shop/electronics[‘electronics’]一个 page.tsx 同时处理列表页和详情页时很实用。5. 路由组Route Groups文件夹名用 (名称) 包裹不出现在 URL 中只用于组织代码、共享布局app/ ├── (marketing)/ │ ├── layout.tsx # 营销站布局大 Banner、无侧边栏 │ ├── page.tsx # / │ └── pricing/page.tsx # /pricing └── (dashboard)/ ├── layout.tsx # 后台布局侧边栏 └── settings/page.tsx # /settings路由组把主站页面聚在一起共用 footer 布局URL 仍是 /、/posts/xxx不会出现 /marketing/…。典型用途多套布局官网 vs 后台按业务模块分文件夹URL 保持扁平 6. 平行路由Parallel Routes用 文件夹名 定义同一布局下的多个独立插槽app/ └── dashboard/ ├── layout.tsx ├── analytics/page.tsx ├── team/page.tsx └── page.tsx// dashboard/layout.tsx export default function Layout({ children, analytics, team, }: { children: React.ReactNode; analytics: React.ReactNode; team: React.ReactNode; }) { return ( {children} {analytics} {team} / ); }适合仪表盘多面板、模态 主内容同时渲染、按条件显示不同区域。学习阶段知道概念即可实战多在复杂后台。7. 拦截路由Intercepting Routes用 (.)、(…)、(…)(…) 等前缀在同一布局内「拦截」导航常用于模态框app/ ├── feed/ │ └── page.tsx ├── photo/ │ └── [id]/page.tsx # 直接访问 /photo/1 → 完整页 └── feed/ └── modal/ └── (.)photo/[id]/page.tsx # 从 /feed 点进 → 弹窗URL 仍是 /photo/1从列表点进详情是弹窗刷新或直接打开 URL 仍是完整页面。Instagram、Twitter 那种「点图弹层」常靠这个实现。8. 私有文件夹Private Folders以下划线 _ 开头的文件夹不会被路由系统识别app/ ├── \_components/ # 仅本 app 下用的组件 ├── \_lib/ # 路由相关工具 └── blog/ └── page.tsx和放在 src/components/ 类似只是把代码就近放在路由旁边Colocation。9. API / 后端路由Route Handlersroute.ts不是 page.tsx定义 HTTP 接口app/api/users/route.tsexportasyncfunctionGET(){returnResponse.json({users:[]});}exportasyncfunctionPOST(request:Request){constbodyawaitrequest.json();returnResponse.json({ok:true});}→ GET/POST /api/users动态 APIapp/api/posts/[id]/route.ts → /api/posts/123对比 Pages Router以前是 pages/api/users.ts现在统一在 app/api/ 下用 route.ts。四、app/ 下的特殊文件约定式文件这些文件名有固定语义Next.js 自动识别文件作用layout.tsx共享 UI可嵌套保留状态page.tsx页面 UI路由可访问loading.tsx加载态Suspense 边界error.tsx错误边界必须是 Client Componentnot-found.tsx404 UI配合 notFound()global-error.tsx根级错误连 layout 都挂了template.tsx类似 layout但每次导航重新挂载default.tsx平行路由的 fallbackroute.tsAPI Route Handleropengraph-image.tsx动态 OG 图sitemap.ts / robots.tsSEOicon.tsx / favicon.ico站点图标嵌套关系示意app/layout.tsx (main)/layout.tsx Suspense 错误时 notFound posts/loading.tsx posts/[slug posts/[slug posts/[slugloading.tsx该段及子路由加载时显示error.tsx该段运行时错误时显示需 ‘use client’not-found.tsx调用 notFound() 或没有匹配动态数据时显示五、布局Layout与模板Template的区别layout.tsxtemplate.tsx导航时保持状态不重新挂载每次导航重新挂载典型用途侧边栏、导航栏入场动画、每次刷新表单大多数场景用 layout 即可。六、Server vs Client 组件与路由App Router 默认是 Server Component// 默认在服务端执行可直接 async 查数据库 export default async function Page() { const data await fetch(...); return div.../div; }需要交互时加 ‘use client’use client; export default function Counter() { const [n, setN] useState(0); ... }与路由的关系page.tsx、layout.tsx 可以是 Server Componenterror.tsx、部分需要 hooks 的 UI 通常是 Client Component推荐页面用 Server交互拆成小 Client 组件七、数据获取与渲染策略和路由强相关方式说明默认 fetchServer Component 里 fetch自动 dedupeexport const dynamic ‘force-static’强制静态export const revalidate 60ISR60 秒重新验证generateStaticParams构建时预生成动态路由export const dynamic ‘force-dynamic’每次请求渲染动态路由静态化示例exportasyncfunctiongenerateStaticParams(){constpostsawaitgetPosts();returnposts.map((p)({slug:p.slug}));}若项目没写 generateStaticParams构建时动态路由会按配置走 SSR 或按需生成。八、根目录其他重要文件middleware.ts项目根或 src/ 下在请求到达页面之前执行Edge Runtimeimport{NextResponse}fromnext/server;importtype{NextRequest}fromnext/server;exportfunctionmiddleware(request:NextRequest){if(!request.cookies.get(token)){returnNextResponse.redirect(newURL(/login,request.url));}}exportconstconfig{matcher:[/dashboard/:path*],};用途鉴权、国际化前缀、A/B、重写 URL。public/public/logo.svg → 浏览器访问 /logo.svg不经 webpack 处理。next.config.ts重定向、图片域名、实验特性、环境变量透传等。九、Pages Router 速览对比学习若看到 pages/ 目录规则不同pages/ ├── index.tsx → / ├── about.tsx → /about ├── posts/[slug].tsx → /posts/:slug └── api/users.ts → /api/users无 layout.tsx 嵌套用 _app.tsx、_document.tsx数据getStaticProps、getServerSideProps、getStaticPaths新项目不建议再用但读文档、维护老库时会遇到十、推荐的学习路线由浅入深静态路由 page.tsxlayout 嵌套动态路由 [slugloading / error / not-found路由组 (group)Route Handlers apimiddleware平行路由 / 拦截路由generateStaticParams / ISR