页面与布局

建议先阅读路由基础定义路由章节再继续。

特殊文件 layout.jspage.jstemplate.js 允许您为路由创建用户界面。本文将指导您如何使用这些特殊文件及其适用场景。

页面

页面是路由的专属用户界面。您可以通过从 page.js 文件默认导出一个组件来定义页面。

例如,要在 app 目录中创建首页,添加 page.js 文件:

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

要创建更多页面,只需新建文件夹并在其中添加 page.js 文件。例如,为 /dashboard 路由创建页面时,新建名为 dashboard 的文件夹并添加 page.js 文件:

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

须知

布局

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

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

例如,以下布局将与 /dashboard/dashboard/settings 页面共享:

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

      {children}
    </section>
  )
}

根布局 (必需)

根布局定义在 app 目录的顶层,适用于所有路由。该布局是必需的,必须包含 htmlbody 标签,以便修改从服务器返回的初始 HTML。

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

嵌套布局

默认情况下,文件夹层次结构中的布局会嵌套,即通过 children 属性包裹子布局。您可以在特定路由段(文件夹)中添加 layout.js 来实现布局嵌套。

例如,要为 /dashboard 路由创建布局,在 dashboard 文件夹中添加新的 layout.js 文件:

嵌套布局
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return <section>{children}</section>
}

如果结合上述两个布局,根布局 (app/layout.js) 将包裹仪表盘布局 (app/dashboard/layout.js),后者又会包裹 app/dashboard/* 中的路由段。

这两个布局的嵌套关系如下:

嵌套布局 UI

须知

模板

模板与布局类似,都会包裹每个子布局或页面。但与跨路由持久化并保持状态的布局不同,模板在导航时会为每个子级创建新实例。这意味着当用户在共享模板的路由间导航时,会挂载组件的新实例、重新创建 DOM 元素、不保留状态且会重新同步副作用。

在某些需要这些特定行为的场景下,模板比布局更合适。例如:

  • 依赖 useEffect 的功能(如记录页面访问)和 useState(如每页反馈表单)
  • 更改框架默认行为。例如,布局中的 Suspense 边界仅在首次加载布局时显示回退内容,而模板会在每次导航时显示回退内容

可以通过从 template.js 文件默认导出一个 React 组件来定义模板。该组件应接收 children 属性。

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

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

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

元数据

app 目录中,您可以使用元数据 API (Metadata APIs) 修改 <head> HTML 元素,如 titlemeta

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

import { Metadata } from 'next'

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

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

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

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

On this page