1.3 页面与路由基础


1.3 页面与路由基础

本节摘要:App Router 的核心约定是"文件系统路由"——文件放对位置,URL 就有了。本节讲清页面(page)、布局(layout)、动态路由([slug])、链接导航(Link)四大基础,并用一张文件系统路由示意建立直观理解。

你能学到什么

阅读完本节,你应当能够:

  1. 用文件系统定义页面 URL
  2. 用 layout 创建共享布局(嵌套布局)
  3. 用 [slug] 定义动态路由并读取参数
  4. 用 Link 做客户端导航(不刷新整页)
  5. 理解 App Router 的路由层级(segment)

问题与直觉:为什么"文件即路由"更好

传统 React 用 react-router 手动配置路由表:<Route path="/about" element={<About/>}>——路由和页面分开维护,改一处漏一处。Next.js 把路由做进文件系统:文件放对位置,URL 自动生成,不存在"路由表与页面不同步"的问题。

直觉类比:文件系统路由像"按目录归档的仓库"——文件在哪个格子(目录),地址就是什么。找东西(加页面)只需"放到对应格子",不用再记一份"仓库地图"(路由表)。

💡 关键直觉:App Router 里,一个"路由段(segment)" = 一个目录app/blog/[slug]/page.tsx 对应 URL /blog/:slug,中间的目录层级就是 URL 的层级。

核心原理:四个核心文件

2.1 page.tsx:页面

文件即页面app/page.tsx/app/about/page.tsx/about

// app/about/page.tsx export default function AboutPage() { return <h1>关于我们</h1>; }

2.2 layout.tsx:共享布局

layout 包裹其下所有页面,切换路由时布局不重新渲染(保留状态):

// app/layout.tsx(根布局,必须有 html/body) export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="zh-CN"> <body> <header>全站导航</header> <main>{children}</main> <footer>全站页脚</footer> </body> </html> ); }

嵌套布局:每个目录都可以有自己的 layout.tsx,自动嵌套:

app/ ├── layout.tsx # 根布局(全站) ├── blog/ │ ├── layout.tsx # 博客布局(博客页共享,如侧边栏) │ └── page.tsx # /blog

2.3 动态路由 [slug]

方括号目录 = 动态参数

// app/blog/[slug]/page.tsx // 文件路径 → /blog/xxx,slug 就是 xxx export default async function BlogPost({ params, }: { params: Promise<{ slug: string }>; }) { const { slug } = await params; // Next.js 15 中 params 是 Promise return <h1>文章:{slug}</h1>; }

多段动态路由app/docs/[version]/[page]/page.tsx/docs/v1/intro

可选参数app/blog/[[slug]]/page.tsx(双层方括号)→ /blog/blog/xxx 都匹配。

import Link from "next/link"; export default function Nav() { return ( <nav> <Link href="/">首页</Link> <Link href="/about">关于</Link> <Link href="/blog/hello-world">文章</Link> </nav> ); }

Link 与 <a> 的区别:Link 做客户端导航——不整页刷新,只加载变化的组件,保留页面状态(滚动位置、输入内容)。Next.js 还会预取视口内的 Link 目标页面,点击时几乎秒开。

工程实践要点:路由层级与导航

3.1 路由层级(segment)总览

3.1 路由层级(segment)总览

3.2 编程式导航与 useRouter

需要"点击按钮后跳转"(非链接场景):

"use client"; import { useRouter } from "next/navigation"; export default function SubmitButton() { const router = useRouter(); return ( <button onClick={() => router.push("/blog")}>去博客</button> ); }

注意 useRouter 只能在客户端组件(带 "use client")中使用。

3.3 404 与路由组

  • 自定义 404app/not-found.tsx(全局)或目录内 not-found.tsx
  • 路由组app/(marketing)/about/page.tsx 的括号目录不产生 URL 段,仅用于组织代码(如区分营销页/应用页)。

常见误区与排查

误区 现象 正解
新建文件不生效 URL 还是 404 确认文件名是 page.tsx 且路径正确
动态路由读不到参数 params 是 Promise const { slug } = await params
Link 整页刷新 用了 <a> 而非 Link 用 next/link 的 Link
布局不生效 layout 文件位置错 布局放在对应目录内
文件同名冲突 启动报错 一个目录只允许一个 page.tsx

动手演练:做一个多页面博客骨架

// 1. app/page.tsx —— 首页,列出文章链接 import Link from "next/link"; const posts = [ { slug: "hello", title: "你好,Next.js" }, { slug: "router", title: "文件系统路由" }, ]; export default function Home() { return ( <ul> {posts.map((p) => ( <li key={p.slug}> <Link href={`/blog/${p.slug}`}>{p.title}</Link> </li> ))} </ul> ); }
// 2. app/blog/layout.tsx —— 博客共享布局 export default function BlogLayout({ children }) { return ( <div> <aside>博客导航</aside> <main>{children}</main> </div> ); }
// 3. app/blog/[slug]/page.tsx —— 动态文章页 export default async function Post({ params, }: { params: Promise<{ slug: string }>; }) { const { slug } = await params; return <h1>文章:{slug}</h1>; }

访问 /blog/hello/blog/router——一个动态路由服务所有文章,这就是文件系统路由的生产力。

本节速览

  • 文件即路由:page.tsx 决定 URL,目录层级 = URL 层级。
  • layout 共享布局:嵌套布局自动包裹子路由,切换路由保留状态。
  • 动态路由:[slug] 目录匹配任意值,params 读取参数(Next.js 15 中是 Promise)。
  • Link 客户端导航:不整页刷新、自动预取、保留状态。
  • 路由组:括号目录只组织代码,不产生 URL。
  • 404:not-found.tsx 自定义全局或局部 404。

深入理解:路由系统的进阶机制

除了 page/layout/dynamic 三个基础,App Router 还有几个实用机制。

并行路由(Parallel Routes):一个路径同时渲染多个独立区域,用命名槽(slot)实现:

app/dashboard/ ├── page.tsx ├── @analytics/page.tsx # 分析面板 └── @settings/page.tsx # 设置面板

适合"仪表盘"式页面——不同区域可以独立加载、独立刷新。

拦截路由(Intercepting Routes):在不改变 URL 的情况下拦截导航,常用于"列表页内点开详情,用模态框展示但 URL 是详情页地址"——用 (.) 前缀约定。社交网站的图片预览、电商的商品快速预览都是典型场景。

路由组与权限:路由组 (auth) 目录不产生 URL 段,但可以有自己的 layout——把登录/注册页放在 (auth)/ 下,给它们一个"无导航"的独立布局,是最常见的用法。

Link 的 prefetch 机制:Link 在视口内出现时会预取目标页面的数据与 JS(默认),点击时几乎秒开。prefetch={false} 可以关闭(流量敏感场景)。理解这个机制,你就明白"为什么 Next.js 的站内跳转特别快"——点击前就已经在准备了

实战决策

场景
普通页面跳转 Link
按钮触发的跳转 useRouter().push
详情模态框 拦截路由
仪表盘多面板 并行路由
登录/注册独立布局 路由组

排查技巧:路由不生效先看"文件是否叫 page.tsx"、"目录层级是否对应 URL"、"是否有同名文件冲突"——三个检查覆盖九成问题。

路由层级结构

目录层级即 URL 层级,layout 包裹子路由,动态段匹配任意参数。

常见问题速答

问:为什么 Next.js 15 的 params 是 Promise?
Next.js 15 为了支持异步组件与流式渲染,把 params/searchParams 改为 Promise,需要用 await params 解包。老代码 params.slug 会失效,迁移时注意。

问:Link 和浏览器刷新有什么区别?
Link 是客户端导航:只加载变化的组件、保留页面状态、自动预取;刷新是整页重新加载。站内导航一律用 Link。

问:布局里能取数据吗?
能。布局是服务器组件,可以 async 取数。但注意:布局不随子路由切换重新渲染,取数只在首次进入布局时执行。

问:动态路由和可选参数什么时候用?
必选参数 [slug] 用于"必须有值"的路径(文章详情);可选参数 [[slug]] 用于"有无皆可"的路径(如分类页,无 slug 显示全部)。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U