Pages 和 Layouts

Pages

page 在路由中是独有的 UI。你可以通过从 page.js文件中导出组件的方式来定义 page 页面。

使用嵌套路由和 page.js文件才能使路由公开可访问。

在 app 文件夹中添加一个page.js文件。

page.js special file

tsx
1// `app/page.tsx` is the UI for the `/` URL
2export default function Page() {
3  return <h1>Hello, Home page!</h1>
4}
tsx
1// `app/dashboard/page.tsx` is the UI for the `/dashboard` URL
2export default function Page() {
3  return <h1>Hello, Dashboard Page!</h1>
4}

page 是服务端组件,但是也可以被设置成 client 组件。

page 可以直接获取数据。

layouts

layouts 是在多个 pages 之间共享的 UI。在导航中,layouts 可以保存状态、交互并且不会重新渲染。layouts 可以被嵌套。

你可以通过在 layout.js中default导出一个 React 组件来定义 layout。

组件应接受一个 childrenprop,这样它的子组件就可以通过它渲染。

layout.js special file

tsx
1export default function DashboardLayout({
2  children, // will be a page or nested layout
3}: {
4  children: React.ReactNode
5}) {
6  return (
7    <section>
8      {/* Include shared UI here e.g. a header or sidebar */}
9      <nav></nav>
10
11      {children}
12    </section>
13  )
14}
  • 最顶级的 layout 被称为Root layout,这个必须的 layout 可以共享给所有页面,Root layout 必须包含 html 和 body。
  • 所有的路由 segment 都可以定义它独有的 layout。这些 layout 在该 segment 中可以被所有页面共享。
  • 默认的 layout 是嵌套的,每个父 layout 都通过children包含它的子 layout。
  • 你可以使用 Route Group(路由分组)去操作特定的路由 segment,进入或者退出共享布局。
  • 默认情况下,layout 是服务端组件。但可以设置成客户端组件。
  • layout 可以获取数据。
  • 父 layout 和子 layout 之间不能传递数据,但是你可以在一个路由中多次获取相同的数据,React 会自动去重请求,不影响性能。
  • layout 不能访问当前的路由 segment,要访问 segment,可以在客户端组件中使用useSelectedLayoutSegment或useSelectedLayoutSegments。
  • 同一个文件夹中定义 layout 和 page 文件,layout 将包裹 page。

Root Layout(必须)

root layout 是定义在 app 文件夹内最顶级的文件,它将和所有路由绑定。

这个 layout 使得修改从服务器返回的初始化 HTML 成为可能。

tsx
1export default function RootLayout({ children }: { children: React.ReactNode }) {
2  return (
3    <html lang="en">
4      <body>{children}</body>
5    </html>
6  )
7}
  • app 文件夹必须包含 root layout
  • root layout 必须定义<html>和body标签。因为 Nextjs 不能自动创建他们。
  • 你可以使用 built-in SEO support 来管理<head>标签。
  • 你可以使用路由分组去创建多个 root layout。example here.
  • root layout 是服务端组件,不能被设置成客户端组件。

从 page 路由迁移过来的话:root layout 可以用来替换_app.js和_document.js文件。

嵌套 layout

layout 在目录中定义,并绑定该目录所映射的特定的 segment,比如app/dashboard/layout.js用来做/dashboard这一 segment 的 UI。

默认情况下,layout 在文件目录结构中是嵌套的,这就意味着得通过children属性包裹它的子 layout。

Nested Layout

tsx
1export default function DashboardLayout({ children }: { children: React.ReactNode }) {
2  return <section>{children}</section>
3}

只有 root layout 才需要包含<html>和<body>。

如果将上面的两个布局组合起来,根布局(app/layout.js)将包裹dashboard布局(app/dashboard/layout.js),而dashboard布局将包裹app/dashboard/*中的路由段。

两个 layout 将会这样被嵌套:

Nested Layouts

你可以使用路由分组来操作指定的 route segment 进入或者退出共享的 layout。

Templates

模板(Templates)与布局(Layouts)类似,它们都包裹每个子布局或页面。但与在整个路由中保持持久性和状态的 Layouts 不同,Templates 在导航时为其每个子组件创建一个新实例。这意味着当用户在共享 Templates 的路由之间导航时,组件的新实例将被挂载,DOM 元素将被重新创建,状态不会被保留,并且效果将重新同步。

在某些情况下,您可能需要这些特定的行为,而模板可能比布局更合适。例如:

  1. 使用 CSS 或动画库的进入/退出动画。
  2. 需要依赖 useEffect(例如记录页面访问)和 useState(例如每个页面的反馈表单)的功能。
  3. 修改默认的框架行为。例如,在 layout 内部的 Suspense 边界仅在第一次加载布局时显示回退内容,而在切换页面时则不显示。而对于 Templates,每次导航都会显示回退内容。

推荐使用 layout,除非你有特殊的原因才选择 Template。

可以通过从 template.js 文件导出默认的 React 组件来定义 template。该组件应该接受一个children属性,该 props 会将被嵌套的 segments 包裹住。

template.js special file

tsx
1export default function Template({ children }: { children: React.ReactNode }) {
2  return <div>{children}</div>
3}

具有 layout 和 template 的路由片段将会被变成这个形式的输出:

tsx
1<Layout>
2  {/* Note that the template is given a unique key. */}
3  <Template key={routeParam}>{children}</Template>
4</Layout>

修改<head>

在 app 目录中,你可以修改<head>元素,比如 title 和 meta 等以支持 SEO。

Meta 属性可以被在 layout.js或者page.hs文件中导出的 metadata object 或 generateMetadata定义。

tsx
1import { Metadata } from 'next'
2
3export const metadata: Metadata = {
4  title: 'Next.js',
5}
6
7export default function Page() {
8  return '...'
9}

在处理注入<title>或者<meta>这类标签时,你不应该手动添加<head>标签。而是应该使用MetadataAPI 去自动处理更高级的需求比如streaming或者消除重复的<head>标签。