Skip to content

NextJS中文文档 - Layout

layout 文件用于在 Next.js 应用程序中定义布局。

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

根布局是位于根 app 目录中的最顶层布局。它用于定义 <html><body> 标签以及其他全局共享的 UI。

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

须知

  • 应用程序必须包含一个根布局。
  • 根布局必须定义 <html><body> 标签,因为 Next.js 不会自动创建它们。
  • 根布局默认替换了 pages 目录中的 _app.js_document.js 文件。
  • Next.js 框架和 React 框架会自动添加 <div> 元素到每个布局和页面中。这些元素是必需的,用于 React 管理渲染,类似于 React 的行为。
  • 布局默认是服务器组件,但也可以设置为客户端组件
  • 父布局和它们的子布局必须使用相同的组件类型:服务器或客户端。默认情况下,它们都是服务器组件,所以会共享相同的类型。
  • 布局可以获取数据。查看数据获取部分了解更多。

Props

children(必需)

布局组件应该接受并使用 children prop。布局会包装一个页面或嵌套布局的内容。例如:

tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <section>
      <nav>
        {/* 共享导航栏 */}
        <ul>
          <li>Home</li>
          <li>About</li>
          <li>Settings</li>
        </ul>
      </nav>
      {children}
    </section>
  )
}
jsx
export default function DashboardLayout({ children }) {
  return (
    <section>
      <nav>
        {/* 共享导航栏 */}
        <ul>
          <li>Home</li>
          <li>About</li>
          <li>Settings</li>
        </ul>
      </nav>
      {children}
    </section>
  )
}

params(可选)

布局 params 对象包含从根布局到包含该布局的所有动态路由段的参数。

例如,如果文件地址是 app/dashboard/[team]/[id]/layout.js,且 URL 是 /dashboard/workspace/123,那么 params 对象会是 { team: 'workspace', id: '123' }

tsx
export default function DashboardLayout({
  children,
  params,
}: {
  children: React.ReactNode
  params: { team: string }
}) {
  return (
    <section>
      {/* 显示当前团队 */}
      <p>团队: {params.team}</p>
      {children}
    </section>
  )
}
jsx
export default function DashboardLayout({ children, params }) {
  return (
    <section>
      {/* 显示当前团队 */}
      <p>团队: {params.team}</p>
      {children}
    </section>
  )
}
Example RouteURLparams
app/dashboard/[team]/layout.js/dashboard/1Promise<{ team: '1' }>
app/shop/[tag]/[item]/layout.js/shop/1/2Promise<{ tag: '1', item: '2' }>
app/blog/[...slug]/layout.js/blog/1/2Promise<{ slug: ['1', '2'] }>
  • Since the params prop is a promise. You must use async/await or React's use function to access the values.
    • In version 14 and earlier, params was a synchronous prop. To help with backwards compatibility, you can still access it synchronously in Next.js 15, but this behavior will be deprecated in the future.

Root Layouts

The app directory must include a root app/layout.js.

tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
jsx
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
  • The root layout must define <html> and <body> tags.
    • You should not manually add <head> tags such as <title> and <meta> to root layouts. Instead, you should use the Metadata API which automatically handles advanced requirements such as streaming and de-duplicating <head> elements.
  • You can use route groups to create multiple root layouts.
    • Navigating across multiple root layouts will cause a full page load (as opposed to a client-side navigation). For example, navigating from /cart that uses app/(shop)/layout.js to /blog that uses app/(marketing)/layout.js will cause a full page load. This only applies to multiple root layouts.

Caveats

How can I access the request object in a layout?

You intentionally cannot access the raw request object in a layout. However, you can access headers and cookies through server-only functions.

Layouts do not rerender. They can be cached and reused to avoid unnecessary computation when navigating between pages. By restricting layouts from accessing the raw request, Next.js can prevent the execution of potentially slow or expensive user code within the layout, which could negatively impact performance.

This design also enforces consistent and predictable behavior for layouts across different pages, which simplifies development and debugging.

Layouts do not receive searchParams

Unlike Pages, Layout components do not receive the searchParams prop. This is because a shared layout is not re-rendered during navigation which could lead to stale searchParams between navigations.

When using client-side navigation, Next.js automatically only renders the part of the page below the common layout between two routes.

For example, in the following directory structure, dashboard/layout.tsx is the common layout for both /dashboard/settings and /dashboard/analytics:

When navigating from /dashboard/settings to /dashboard/analytics, page.tsx in /dashboard/analytics will rerender on the server, while dashboard/layout.tsx will not rerender because it's a common UI shared between the two routes.

This performance optimization allows navigation between pages that share a layout to be quicker as only the data fetching and rendering for the page has to run, instead of the entire route that could include shared layouts that fetch their own data.

Because dashboard/layout.tsx doesn't re-render, the searchParams prop in the layout Server Component might become stale after navigation.

Instead, use the Page searchParams prop or the useSearchParams hook in a Client Component within the layout, which is rerendered on the client with the latest searchParams.

Layouts cannot access pathname

Layouts cannot access pathname. This is because layouts are Server Components by default, and don't rerender during client-side navigation, which could lead to pathname becoming stale between navigations. To prevent staleness, Next.js would need to refetch all segments of a route, losing the benefits of caching and increasing the RSC payload size on navigation.

Instead, you can extract the logic that depends on pathname into a Client Component and import it into your layouts. Since Client Components rerender (but are not refetched) during navigation, you can use Next.js hooks such as usePathname to access the current pathname and prevent staleness.

tsx
import { ClientComponent } from '@/app/ui/ClientComponent'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <ClientComponent />
      {/* Other Layout UI */}
      <main>{children}</main>
    </>
  )
}
jsx
import { ClientComponent } from '@/app/ui/ClientComponent'

export default function Layout({ children }) {
  return (
    <>
      <ClientComponent />
      {/* Other Layout UI */}
      <main>{children}</main>
    </>
  )
}

Common pathname patterns can also be implemented with params prop.

See the examples section for more information.

Examples

Displaying content based on params

Using dynamic route segments, you can display or fetch specific content based on the params prop.

tsx
export default async function DashboardLayout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Promise<{ team: string }>
}) {
  const { team } = await params

  return (
    <section>
      <header>
        <h1>Welcome to {team}'s Dashboard</h1>
      </header>
      <main>{children}</main>
    </section>
  )
}
jsx
export default async function DashboardLayout({ children, params }) {
  const { team } = await params

  return (
    <section>
      <header>
        <h1>Welcome to {team}'s Dashboard</h1>
      </header>
      <main>{children}</main>
    </section>
  )
}

Reading params in Client Components

To use params in a Client Component (which cannot be async), you can use React's use function to read the promise:

tsx
'use client'

import { use } from 'react'

export default function Page({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = use(params)
}
js
'use client'

import { use } from 'react'

export default function Page({ params }) {
  const { slug } = use(params)
}

Version History

VersionChanges
v15.0.RCparams is now a promise. A codemod is available.
v13.0.0layout introduced.

良好实践

使用 Web 标准

Next.js 鼓励使用 Web 平台的标准元素和功能。

例如,在创建根布局时,Next.js 不会自动添加 <html><body> 标签。它们由你显式添加,使得在这些元素上设置如 lang 属性或访问第三方脚本的 <body> 标签更加简单。

流布局

布局支持使用 React 的流式传输和 Suspense boundaries。这允许从服务器到客户端逐步流式传输布局,包括即时显示已加载部分的 UI。同时为其他部分加载 UI 的后备内容

例如,你可以在导航带下使用 Suspense 边界来流式传输产品页面的内容:

tsx
import { Suspense } from 'react'
import { ProductHeader, ProductTabs, ProductFeed } from 'components/product'

export default async function Layout({
  children,
  params,
}: {
  children: React.ReactNode
  params: { id: string }
}) {
  const { id } = params
  return (
    <>
      <ProductHeader id={id} />
      <ProductTabs id={id} />
      <Suspense fallback={<ProductFeed.Skeleton />}>
        <ProductFeed id={id} />
      </Suspense>
      {children}
    </>
  )
}
jsx
import { Suspense } from 'react'
import { ProductHeader, ProductTabs, ProductFeed } from 'components/product'

export default async function Layout({ children, params }) {
  const { id } = params
  return (
    <>
      <ProductHeader id={id} />
      <ProductTabs id={id} />
      <Suspense fallback={<ProductFeed.Skeleton />}>
        <ProductFeed id={id} />
      </Suspense>
      {children}
    </>
  )
}

使用 Suspense 的更多信息,请参阅加载 UIStreaming

示例

Root Layout

下面是一个与 Tailwind CSS 一起使用的根布局示例:

tsx
import '@/styles/globals.css'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
jsx
import '@/styles/globals.css'

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

须知

  • 根布局默认替换了 _app.js_document.js 文件。
  • 你可以使用 head.js 文件 来定义页面的头部元数据。

创建嵌套布局

要创建嵌套布局,请在文件夹内添加布局文件,该布局将应用于特定的路由段并在其中渲染任何子布局。

例如,如果要创建如下所示的嵌套布局:

  • app
    • layout.js(根布局)
    • dashboard
      • layout.js(仪表板布局)
      • page.js

根布局(app/layout.js)将包装仪表板布局(app/dashboard/layout.js),该布局将包装 app/dashboard/page.js

tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <header>我在每个页面上</header>
        {children}
        <footer>我在每个页面上</footer>
      </body>
    </html>
  )
}
jsx
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <header>我在每个页面上</header>
        {children}
        <footer>我在每个页面上</footer>
      </body>
    </html>
  )
}
tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <section>
      <nav>
        <ul>
          <li>首页</li>
          <li>关于</li>
          <li>设置</li>
        </ul>
      </nav>
      {children}
    </section>
  )
}
jsx
export default function DashboardLayout({ children }) {
  return (
    <section>
      <nav>
        <ul>
          <li>首页</li>
          <li>关于</li>
          <li>设置</li>
        </ul>
      </nav>
      {children}
    </section>
  )
}

带元数据的布局

你可以定义页面的元数据,这些元数据会导出到布局和页面中。

tsx
import { Metadata } from 'next'

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

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
jsx
export const metadata = {
  title: 'Next.js',
}

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

查看元数据 API 参考了解更多信息。

将状态保持在导航之间

布局可以包装多个页面,这种情况下布局在导航过程中是保持状态的,而不会重新渲染。这可以通过使用客户端组件和 React 的状态来实现。

tsx
'use client'

import { useState } from 'react'
import { useSelectedLayoutSegment } from 'next/navigation'

export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  const [isOpen, setIsOpen] = useState(false)
  const segment = useSelectedLayoutSegment()

  return (
    <section>
      <p onClick={() => setIsOpen(!isOpen)}>仪表板(当前:{segment})</p>
      {isOpen && <div>显示更多...</div>}
      {children}
    </section>
  )
}
jsx
'use client'

import { useState } from 'react'
import { useSelectedLayoutSegment } from 'next/navigation'

export default function DashboardLayout({ children }) {
  const [isOpen, setIsOpen] = useState(false)
  const segment = useSelectedLayoutSegment()

  return (
    <section>
      <p onClick={() => setIsOpen(!isOpen)}>仪表板(当前:{segment})</p>
      {isOpen && <div>显示更多...</div>}
      {children}
    </section>
  )
}

须知:在页面组件重新渲染时,客户端组件发送的布局不会在导航过程中重新渲染。查看客户端组件了解更多信息。

从布局中获取数据

从布局中获取数据时有多种选择,包括使用 fetch API 或 React 服务器组件。这些方法在数据获取部分有详细描述。

tsx
export default async function Layout({ children }: { children: React.ReactNode }) {
  const categories = await getCategories()

  return (
    <section>
      <aside>
        <nav>
          <ul>
            {categories.map((category) => (
              <li key={category.id}>{category.name}</li>
            ))}
          </ul>
        </nav>
      </aside>
      {children}
    </section>
  )
}
jsx
export default async function Layout({ children }) {
  const categories = await getCategories()

  return (
    <section>
      <aside>
        <nav>
          <ul>
            {categories.map((category) => (
              <li key={category.id}>{category.name}</li>
            ))}
          </ul>
        </nav>
      </aside>
      {children}
    </section>
  )
}

须知:在上面的示例中,getCategories() 表示一个自定义函数,返回类别数据。

版本历史

版本变更
v13.0.0layout 引入。

🎉有任何问题,欢迎联系我

WeChat QR Code
WeChat
QQ QR Code
QQ

赣ICP备2023003243号