Skip to main content

布局和模板

特殊文件 layout.jstemplate.js 允许你创建在 routes 之间共享的 UI。本页将指导你了解如何以及何时使用这些特殊文件。

¥The special files layout.js and template.js allow you to create UI that is shared between routes. This page will guide you through how and when to use these special files.

布局

¥Layouts

布局是在多个路由之间共享的 UI。在导航时,布局保留状态、保持交互性并且不重新渲染。布局也可以是 nested

¥A layout is UI that is shared between multiple routes. On navigation, layouts preserve state, remain interactive, and do not re-render. Layouts can also be nested.

你可以通过默认从 layout.js 文件导出 React 组件来定义布局。该组件应该接受 children 属性,该属性将在渲染期间填充子布局(如果存在)或页面。

¥You can define a layout by default exporting a React component from a layout.js file. The component should accept a children prop that will be populated with a child layout (if it exists) or a page during rendering.

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

¥For example, the layout will be shared with the /dashboard and /dashboard/settings pages:

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>
)
}
export default function DashboardLayout({
children, // will be a page or nested layout
}) {
return (
<section>
{/* Include shared UI here e.g. a header or sidebar */}
<nav></nav>

{children}
</section>
)
}

根布局(必需)

¥Root Layout (Required)

根布局在 app 目录的顶层定义并适用于所有路由。此布局是必需的,并且必须包含 htmlbody 标记,允许你修改从服务器返回的初始 HTML。

¥The root layout is defined at the top level of the app directory and applies to all routes. This layout is required and must contain html and body tags, allowing you to modify the initial HTML returned from the server.

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

嵌套布局

¥Nesting Layouts

默认情况下,文件夹层次结构中的布局是嵌套的,这意味着它们通过 children 属性封装子布局。你可以通过在特定路由段(文件夹)内添加 layout.js 来嵌套布局。

¥By default, layouts in the folder hierarchy are nested, which means they wrap child layouts via their children prop. You can nest layouts by adding layout.js inside specific route segments (folders).

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

¥For example, to create a layout for the /dashboard route, add a new layout.js file inside the dashboard folder:

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

如果要组合上面的两个布局,根布局 (app/layout.js) 将封装仪表板布局 (app/dashboard/layout.js),仪表板布局 (app/dashboard/layout.js) 将封装路由段在 app/dashboard/* 内。

¥If you were to combine the two layouts above, the root layout (app/layout.js) would wrap the dashboard layout (app/dashboard/layout.js), which would wrap route segments inside app/dashboard/*.

这两个布局将这样嵌套:

¥The two layouts would be nested as such:

很高兴知道:

¥Good to know:

  • .js.jsx.tsx 文件扩展名可用于布局。

    ¥.js, .jsx, or .tsx file extensions can be used for Layouts.

  • 只有根布局可以包含 <html><body> 标签。

    ¥Only the root layout can contain <html> and <body> tags.

  • layout.jspage.js 文件定义在同一文件夹中时,布局将换行页面。

    ¥When a layout.js and page.js file are defined in the same folder, the layout will wrap the page.

  • 默认布局为 服务器组件,但可以设置为 客户端组件

    ¥Layouts are Server Components by default but can be set to a Client Component.

  • 布局可以获取数据。查看 数据获取 部分了解更多信息。

    ¥Layouts can fetch data. View the Data Fetching section for more information.

  • 在父布局与其子布局之间传递数据是不可能的。然而,你可以在一个路由中多次获取相同的数据,React 将 自动删除请求的重复数据 而不影响性能。

    ¥Passing data between a parent layout and its children is not possible. However, you can fetch the same data in a route more than once, and React will automatically dedupe the requests without affecting performance.

  • 布局无法访问 pathname (了解更多)。但导入的客户端组件可以使用 usePathname 钩子访问路径名。

    ¥Layouts do not have access to pathname (learn more). But imported Client Components can access the pathname using usePathname hook.

  • 布局无法访问其自身下方的路由段。要访问所有路由段,你可以在客户端组件中使用 useSelectedLayoutSegmentuseSelectedLayoutSegments

    ¥Layouts do not have access to the route segments below itself. To access all route segments, you can use useSelectedLayoutSegment or useSelectedLayoutSegments in a Client Component.

  • 你可以使用 路由组 在共享布局中选择特定路由段。

    ¥You can use Route Groups to opt specific route segments in and out of shared layouts.

  • 你可以使用 路由组 创建多个根布局。参见 例子在这里

    ¥You can use Route Groups to create multiple root layouts. See an example here.

  • pages 目录迁移:根布局替换 _app.js_document.js 文件。查看迁移指南

    ¥Migrating from the pages directory: The root layout replaces the _app.js and _document.js files. View the migration guide.

模板

¥Templates

模板与布局类似,它们封装子布局或页面。与跨路由持续存在并维护状态的布局不同,模板为导航上的每个子级创建一个新实例。这意味着当用户在共享模板的路由之间导航时,会安装子级的新实例,重新创建 DOM 元素,客户端组件中不会保留状态,并且会重新同步效果。

¥Templates are similar to layouts in that they wrap a child layout or page. Unlike layouts that persist across routes and maintain state, templates create a new instance for each of their children on navigation. This means that when a user navigates between routes that share a template, a new instance of the child is mounted, DOM elements are recreated, state is not preserved in Client Components, and effects are re-synchronized.

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

¥There may be cases where you need those specific behaviors, and templates would be a more suitable option than layouts. For example:

  • 在导航上重新同步 useEffect

    ¥To resynchronize useEffect on navigation.

  • 重置导航时子客户端组件的状态。

    ¥To reset the state of a child Client Components on navigation.

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

¥A template can be defined by exporting a default React component from a template.js file. The component should accept a children prop.

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

在嵌套方面,template.js 在布局及其子布局之间渲染。这是一个简化的输出:

¥In terms of nesting, template.js is rendered between a layout and its children. Here's a simplified output:

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

示例

¥Examples

元数据

¥Metadata

你可以使用 元数据 API 修改 <head> HTML 元素,例如 titlemeta

¥You can modify the <head> HTML elements such as title and meta using the Metadata APIs.

可以通过导出 layout.jspage.js 文件中的 metadata 对象generateMetadata 功能 来定义元数据。

¥Metadata can be defined by exporting a metadata object or generateMetadata function in a layout.js or page.js file.

import type { 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,它会自动处理高级要求,例如流式传输和删除 <head> 元素的重复数据。

¥Good to know: You should not manually add <head> tags such as <title> and <meta> to root layouts. Instead, use the Metadata API which automatically handles advanced requirements such as streaming and de-duplicating <head> elements.

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

¥Learn more about available metadata options in the API reference.

¥Active Nav Links

你可以使用 usePathname() 钩子来确定导航链接是否处于活动状态。

¥You can use the usePathname() hook to determine if a nav link is active.

由于 usePathname() 是客户端钩子,因此你需要将导航链接提取到客户端组件中,该组件可以导入到你的布局或模板中:

¥Since usePathname() is a client hook, you need to extract the nav links into a Client Component, which can be imported into your layout or template:

'use client'

import { usePathname } from 'next/navigation'
import Link from 'next/link'

export function NavLinks() {
const pathname = usePathname()

return (
<nav>
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
Home
</Link>

<Link
className={`link ${pathname === '/about' ? 'active' : ''}`}
href="/about"
>
About
</Link>
</nav>
)
}
'use client'

import { usePathname } from 'next/navigation'
import Link from 'next/link'

export function Links() {
const pathname = usePathname()

return (
<nav>
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
Home
</Link>

<Link
className={`link ${pathname === '/about' ? 'active' : ''}`}
href="/about"
>
About
</Link>
</nav>
)
}
import { NavLinks } from '@/app/ui/nav-links'

export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<NavLinks />
<main>{children}</main>
</body>
</html>
)
}
import { NavLinks } from '@/app/ui/nav-links'

export default function Layout({ children }) {
return (
<html lang="en">
<body>
<NavLinks />
<main>{children}</main>
</body>
</html>
)
}