路由
每个应用程序的骨架都是路由。 本页面将向您介绍 Web 路由的基本概念以及如何在 Next.js 中处理路由。
术语
首先,您将看到整个文档中使用了这些术语。这是一个快速参考:

- 树:用于可视化层次结构的一种约定。例如,一个具有父组件和子组件的组件树,一个文件夹结构等。
- 子树:树的一部分,以新的根(首个)开始,以叶子(最后)结束。
- 根:树或子树中的第一个节点,例如根布局。
- 叶子:子树中没有子节点的节点,例如URL路径中的最后一段。"

- URL 段:由斜杠分隔的 URL 路径的一部分。
- URL 路径:域名后面的 URL 部分(由段组成)
app路由
在第13版本中,Next.js引入了一个基于 React Server Components 构建的新的App Router,支持共享布局、嵌套路由、加载状态、错误处理等功能。
App Router在一个名为app的新目录中工作。
该app目录与pages目录一起工作,以支持渐进式采用。
这使您可以将应用程序的某些路由选择为新行为,同时保留pages目录中的其他路由以保持先前的行为。
如果您的应用程序使用pages目录,
请同时查看Pages Router文档。
App Router优先于Pages Router。 跨目录的路由不应解析为相同的URL路径, 否则将导致构建时错误以防止冲突。
默认情况下,app目录中的组件是
React Server Components。
这是一种性能优化,使您能够轻松采用它们,
您还可以使用Client Components。
如果您是Server Components的新手, 请查看Server页面。
文件夹和文件的角色
Next.js使用基于文件系统的路由器,其中:
路由段
路由中的每个文件夹表示一个路由段。每个路由段都映射到URL路径中相应的段。

嵌套路由
要创建嵌套路由,可以将文件夹嵌套在彼此内部。
例如,您可以通过在app目录中嵌套两个新文件夹来添 加一个新的/dashboard/settings路由。
/dashboard/settings路由由三个段组成:
/(根段)dashboard(段)settings(叶段)
文件约定
Next.js提供了一组特殊文件,以在嵌套路由中创建具有特定行为的UI:
layout:用于一个段及其子代的共享UI。page:路由的唯一UI,使路由公开可访问。loading:一个段及其子代的加载UI。not-found:一个段及其子代的未找到UI。error:一个段及其子代的错误UI。global-error:全局错误UI。route:服务器端API端点。template:专门重新呈现的布局UI。default:并行路由的回退UI。
.js、.jsx或.tsx文件扩展名可用于特殊文件。
组件层次结构
路由段中特殊文件定义的React组件以特定的层次结构呈现:
layout.jstemplate.jserror.js(React错误边界)loading.js(React悬停边界)not-found.js(React错误边界)page.js或嵌套的layout.js

在嵌套路由中,段的组件将嵌套在其父段的组件内。

同地放置
除了特殊文件外,您还可以选择将您自己的文件(例如组件、样式、测试等)同地放置在app目录中的文件夹中。
这是因为虽然文件夹定义路由,但只有由`page.js`或`route.js`返回的内容是公开可寻址的。

高级路由模式
App Router还提供了一组约定,帮助您实现更高级的路由模式。这些包括:
- 并行路由:允许您同时在同一视图中显示两个或更多可以独立导航的页面。您可以用于具有自己子导航的分屏视图,例如仪表板。
- 拦截路由:允许您拦截路由并在另一个路由的上下文中显示它。当保持当前页面的上下文很重要时,您可以使用这些功能。例如,在编辑一个任务时查看所有任务或在动态源中展开照片。
这些模式使您能够构建更丰富和复杂的UI,使过去对小团队和个人开发人员来说历来复杂的功能变得更加平民化。
定义路由
创建路由
Next.js使用基于文件系统的路由器,其中文件夹用于定义路由。
每个文件夹表示一个路由段,对应到一个URL段。
要创建嵌套路由,您可以将文件夹嵌套在彼此内部。

使用特殊的page.js文件可以使路由段公开可访问。

在这个例子中,/dashboard/analytics的URL路径是不公开可访问的,
因为它没有相应的page.js文件。
这个文件夹可以用于存储组件、样式表、图像或其他同地放置的文件。
特殊文件可以使用.js、.jsx或.tsx文件扩展名。
创建UI
使用特殊文件约定来为每个路由段创建UI。
最常见的是用于显示路由独特UI的pages和用于显示跨多个路由共享UI的layouts。
例如,要创建您的第一个页面,请在app目录中添加一个page.js文件,并导出一个React组件:
import React from 'react';
const Page: React.FC = () => {
return <h1>Hello, Next.js!</h1>;
};
export default Page;
页面和布局
Next.js 13 中的 App Router 引入了新的文件约定,可以轻松创建页面、共享布局和模板。 本页面将指导您如何在 Next.js 应用程序中使用这些特殊文件。
页面
页面是特定路由的UI。
您可以通过从page.js文件中导出组件来定义页面。
使用嵌套文件夹来定义路由,并使用page.js文件使路由公开可访问。
通过在app目录中添加page.js文件来创建您的第一个页面:

// `app/page.tsx` is the UI for the `/` URL
export default function Page() {
return <h1>Hello, Home page!</h1>
}
// `app/dashboard/page.tsx` is the UI for the `/dashboard` URL
export default function Page() {
return <h1>Hello, Dashboard Page!</h1>
}
值得知道:
- 页面始终是路由子树的叶子。
- 可以使用
.js、.jsx或.tsx文件扩展名用于页面。 - 必须使用
page.js文件才能使路由段公开可访问。 - 页面默认是Server Components,但可以设置为Client Components。
- 页面可以获取数据。有关更多信息,请查看数据获取部分。
布局
布局是在多个页面之间共享的UI。 在导航时,布局保留状态,保持交互,并且不重新呈现。 布局也可以是嵌套的。
您可以通过从layout.js文件中默认导出一个React组件来定义布局。
组件应接受一个children属性,在渲染期间将其填充为子布局(如果存在)或子页面。

export default function DashboardLayout({
children, // will be a page or nested layout
}: {
children: React.ReactNode
}) {
return (
<section>
{/* Include shared UI here e.g. a header or sidebar */}
<nav></nav>
{children}
</section>
)
}
值得知道:
- 最上层的布局称为根布局。
这是一个必需的布局,它在应用程序中的所有页面之间共享。
根布局必须包含
html和body标签。 - 任何路由段都可以选择性地定义自己的布局。这些布局将在该段中的所有页面之间共享。
- 路由中的布局默认是嵌套的。每个父布局使用React
children属性包装其下方的子布局。 - 您可以使用Route Groups选择性地将特定路由段放入和移出共享布局。
- 布局默认是Server Components,但可以设置为Client Components。
- 布局可以获取数据。有关更多信息,请查看数据获取部分。
- 在父布局和其子布局之间传递数据是不可能的。 但是,您可以在路由中多次获取相同的数据,而React将自动去重请求,而不会影响性能。
- 布局无法访问其下方的路由段。要访问所有路由段,可以在Client Component中使用
useSelectedLayoutSegment或useSelectedLayoutSegments。 - 可以使用
.js、.jsx或.tsx文件扩展名用于布局。 - 可以在同一文件夹中定义
layout.js和page.js文件。布局将包裹页面。
根布局(必需)
根布局在app目录的顶层定义,并应用于所有路由。此布局使您能够修改从服务器返回的初始HTML。
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
值得知道:
app目录必须包含一个根布局。- 根布局必须定义
<html>和<body>标签,因为Next.js不会自动创建它们。 - 您可以使用内置的SEO支持来管理
<head>HTML元素,例如<title>元素。 - 您可以使用路由组创建多个根布局。查看此处的示例。
- 根布局默认是Server Components,不能设置为Client Components。
- 从
pages目录迁移:根布局替代了_app.js和_document.js文件。查看迁移指南。
嵌套布局
在文件夹中定义的布局(例如app/dashboard/layout.js)
适用于特定的路由段(例如acme.com/dashboard),
并在这些段处于活动状态时呈现。
默认情况下,文件层次结构中的布局是嵌套的,
这意味着它们通过其children属性包装子布局。

export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return <section>{children}</section>
}
值得知道:
- 只有根布局可以包含
<html>和<body>标签。
如果将上述两个布局合并,
根布局(app/layout.js)将包装仪表板布局(app/dashboard/layout.js),
后者将包装app/dashboard/*内的路由段。
这两个布局将嵌套如下:

可以使用Route Groups选择性地将特定路由段放入和移出共享布局。
模板
模板与布局相似,因为它们包装每个子布局或页面。 与布局不同的是,模板为导航中的每个子项创建一个新实例。 这意味着当用户在共享模板的路由之间导航时,该组件的新实例被挂载, DOM元素被重新创建,状态不保留,并且效果重新同步。
可能有些情况下,您需要这些特定的行为,而模板将比布局更合适。例如:
- 依赖于
useEffect(例如记录页面视图)和useState(例如每页反馈表单)的功能。 - 更改默认框架行为。例如,布局内的Suspense边界仅在第一次加载布局时显示回退,而在切换页面时不会显示。对于模板,每次导航都会显示回退。
可以通过从template.js文件中导出一个默认的React组件来定义模板。组件应该接受一个children属性。

export default function Template({ children }: { children: React.ReactNode }) {
return <div>{children}</div>
}
就嵌套而言,template.js在布局和其子布局之间呈现。以下是一个简化的输出:
<Layout>
{/* 注意,模板被赋予唯一的键。 */}
<Template key={routeParam}>{children}</Template>
</Layout>
修改<head>
在app目录中,您可以使用内置的SEO支持来修改<head> HTML元素,例如标题和meta。
可以通过在layout.js或page.js文件中导出一个metadata对象或
generateMetadata函数来定义元数据。
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Next.js',
};
export default function Page() {
return '...';
}
您不应手动添加<head>标签(如<title>和<meta>)到根布局。
相反,您应该使用Metadata API,该API会自动处理高级要求,如流式传输和去重<head>元素。
链接和导航
在Next.js中,有两种在路由之间导航的方式:
- 使用
<Link>组件 - 使用
useRouterHook
本页面将介绍如何使用 <Link>、useRouter(),并深入探讨导航的工作原理。
<Link> 组件
<Link> 是一个内置组件,它扩展了 HTML 的 <a> 标签,
提供了在路由之间进行预取和客户端导航的功能。
这是在Next.js中导航之间的主要方式。
您可以通过从 next/link 中导入它,并向组件传递一个 href 属性来使用它:
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
还可以向 <Link> 传递其他可选的属性。更多信息请参阅 API 参考。
示例
链接到动态段
在链接到动态段时,您可以使用模板文字和插值来生成链接列表。例如,要生成博客文章列表:
import Link from 'next/link'
export default function PostList({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</li>
))}
</ul>
)
}
检查活动链接
您可以使用 usePathname() 来确定链接是否处于活动状态。
例如,要向活动链接添加类,您可以检查当前pathname是否与链接的 href 匹配:
'use client'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
export function Links() {
const pathname = usePathname()
return (
<nav>
<ul>
<li>
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
Home
</Link>
</li>
<li>
<Link
className={`link ${pathname === '/about' ? 'active' : ''}`}
href="/about"
>
About
</Link>
</li>
</ul>
</nav>
)
}
滚动到id
Next.js App Router 的默认行为是在导航时滚动到新路由的顶部,或者对于后退和前进导航保持滚动位置。
如果您想要在导航时滚动到特定的标识,可以在URL后附加一个 # 锚链接,
或者只是将一个 # 锚链接传递给 href 属性。这是因为 <Link> 渲染为 <a> 元素,所以是可能的。
<Link href="/dashboard#settings">Settings</Link>
// 输出
<a href="/dashboard#settings">Settings</a>
禁用滚动恢复
Next.js App Router 的默认行为是在导航时滚动到新路由的顶部,或者对于后退和前进导航保持滚动位置。
如果您想要禁用此行为,可以将 scroll={false} 传递给 <Link> 组件,
或者将 scroll: false 传递给 router.push() 或 router.replace()。
// next/link
<Link href="/dashboard" scroll={false}>
Dashboard
</Link>
// useRouter
import { useRouter } from 'next/navigation'
const router = useRouter()
router.push('/dashboard', { scroll: false })
useRouter() Hook
useRouter 钩子允许您以编程方式更改路由。
此钩子只能在客户端组件内使用,并且是从 next/navigation 导入的。
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
有关 useRouter 方法的完整列表,请参阅 API 参考。
除非有使用 useRouter 的特定需求,否则请使用 <Link> 组件在路由之间进行导航。