页面与布局

建议先阅读路由基础定义路由页面再继续。

Next.js 13 中的应用路由 (App Router) 引入了新的文件约定,可轻松创建页面共享布局模板。本指南将介绍如何在 Next.js 应用中使用这些特殊文件。

页面

页面是路由对应的唯一用户界面。您可以通过从 page.js 文件导出组件来定义页面。使用嵌套文件夹定义路由,并通过 page.js 文件使路由可公开访问。

app 目录中添加 page.js 文件来创建您的第一个页面:

page.js 特殊文件
// `app/page.tsx` 是 `/` 路由对应的界面
export default function Page() {
  return <h1>你好,首页!</h1>
}
// `app/page.js` 是 `/` 路由对应的界面
export default function Page() {
  return <h1>你好,首页!</h1>
}
// `app/dashboard/page.tsx` 是 `/dashboard` 路由对应的界面
export default function Page() {
  return <h1>你好,仪表盘页面!</h1>
}
// `app/dashboard/page.js` 是 `/dashboard` 路由对应的界面
export default function Page() {
  return <h1>你好,仪表盘页面!</h1>
}

须知

布局

布局是多个页面间共享的用户界面。在导航时,布局会保持状态、维持交互且不会重新渲染。布局还可以嵌套

您可以通过从 layout.js 文件默认导出一个 React 组件来定义布局。该组件应接收 children 属性,该属性在渲染时会被填充为子布局(如果存在)或子页面。

layout.js 特殊文件
export default function DashboardLayout({
  children, // 将是一个页面或嵌套布局
}: {
  children: React.ReactNode
}) {
  return (
    <section>
      {/* 在此处添加共享 UI 如页眉或侧边栏 */}
      <nav></nav>

      {children}
    </section>
  )
}
export default function DashboardLayout({
  children, // 将是一个页面或嵌套布局
}) {
  return (
    <section>
      {/* 在此处添加共享 UI 如页眉或侧边栏 */}
      <nav></nav>

      {children}
    </section>
  )
}

须知

  • 最顶层的布局称为根布局 (Root Layout)。这个必需的布局会被应用中的所有页面共享。根布局必须包含 htmlbody 标签
  • 任何路由段都可以选择定义自己的布局。这些布局将在该段的所有页面间共享
  • 路由中的布局默认会嵌套。每个父布局会通过 React 的 children 属性包裹其下方的子布局
  • 您可以使用路由组 (Route Groups)来选择性地包含或排除特定路由段的共享布局
  • 布局默认为服务端组件 (Server Components),但可设置为客户端组件 (Client Component)
  • 布局可以获取数据。查看数据获取章节了解更多
  • 无法在父布局与其子级之间传递数据。但您可以在路由中多次获取相同数据,React 会自动去重请求且不影响性能
  • 布局无法访问其下方的路由段。要在客户端组件中访问所有路由段,可使用 useSelectedLayoutSegmentuseSelectedLayoutSegments
  • 可使用 .js.jsx.tsx 文件扩展名定义布局
  • 同一文件夹中可同时定义 layout.jspage.js 文件。布局会包裹页面

根布局 (必需)

根布局定义在 app 目录的顶层,适用于所有路由。此布局允许您修改从服务器返回的初始 HTML。

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

须知

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>
}
export default function DashboardLayout({ children }) {
  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 属性。

template.js 特殊文件
export default function Template({ children }: { children: React.ReactNode }) {
  return <div>{children}</div>
}
export default function Template({ children }) {
  return <div>{children}</div>
}

在嵌套方面,template.js 会在布局与其子级之间渲染。以下是简化输出:

输出
<Layout>
  {/* 注意模板具有唯一键 */}
  <Template key={routeParam}>{children}</Template>
</Layout>

修改 <head>

app 目录中,您可以使用内置 SEO 支持修改 <head> HTML 元素,如 titlemeta

通过在 layout.jspage.js 文件中导出 metadata 对象generateMetadata 函数来定义元数据。

import { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Next.js',
}

export default function Page() {
  return '...'
}
export const metadata = {
  title: 'Next.js',
}

export default function Page() {
  return '...'
}

须知:您不应手动添加 <head> 标签(如 <title><meta>)到根布局中。而应使用元数据 API (Metadata API),它能自动处理高级需求,如流式传输和去重 <head> 元素。

在 API 参考中了解可用的元数据选项。