Skip to content

layout.js

layout 文件用于定义 Next.js 应用中的布局。

¥The layout file is used to define a layout in your Next.js application.

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

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

¥A root layout is the top-most layout in the root app directory. It is used to define the <html> and <body> tags and other globally shared UI.

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

参考

¥Reference

属性

¥Props

children(必需的)

¥children (required)

布局组件应该接受并使用 children 属性。在渲染期间,children 将填充布局正在环绕的路由段。这些主要是子 布局(如果存在)或 页面 的组件,但也可能是其他特殊文件,如适用时的 加载中错误

¥Layout components should accept and use a children prop. During rendering, children will be populated with the route segments the layout is wrapping. These will primarily be the component of a child Layout (if it exists) or Page, but could also be other special files like Loading or Error when applicable.

params(可选)

¥params (optional)

从根段到该布局解析为包含 动态路由参数 对象的对象的 promise。

¥A promise that resolves to an object containing the dynamic route parameters object from the root segment down to that layout.

tsx
export default async function Layout({
  params,
}: {
  params: Promise<{ team: string }>
}) {
  const { team } = await params
}
示例路由URLparams
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'] }>
  • 由于 params prop 是一个 promise。你必须使用 async/await 或 React 的 use 函数来访问值。

    ¥Since the params prop is a promise. You must use async/await or React's use function to access the values.

    • 在版本 14 及更早版本中,params 是一个同步 prop。为了帮助向后兼容,你仍然可以在 Next.js 15 中同步访问它,但此行为将来会被弃用。

      ¥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 Layout

app 目录必须包含根 app/layout.js

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

tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>{children}</body>
    </html>
  )
}
  • 根布局必须定义 <html><body> 标签。

    ¥The root layout must define <html> and <body> tags.

    • 你不应手动将 <head> 标签(例如 <title><meta>)添加到根布局。相反,你应该使用 元数据 API,它会自动处理高级要求,例如流式传输和删除 <head> 元素的重复数据。

      ¥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.

    • 跨多个根布局导航将导致完整页面加载(与客户端导航相反)。例如,从使用 app/(shop)/layout.js/cart 导航到使用 app/(marketing)/layout.js/blog 将导致整页加载。这仅适用于多个根布局。

      ¥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

请求对象

¥Request Object

布局在导航期间会缓存在客户端,以避免不必要的服务器请求。

¥Layouts are cached in the client during navigation to avoid unnecessary server requests.

布局 不会重新渲染。它们可以被缓存和重用,以避免在页面之间导航时不必要的计算。通过限制布局访问原始请求,Next.js 可以防止布局内执行可能缓慢或昂贵的用户代码,这可能会对性能产生负面影响。

¥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.

要访问请求对象,你可以使用 服务器组件 和函数中的 headerscookies API。

¥To access the request object, you can use headers and cookies APIs in Server Components and Functions.

tsx
import { cookies } from 'next/headers'

export default async function Layout({ children }) {
  const cookieStore = await cookies()
  const theme = cookieStore.get('theme')
  return '...'
}

查询参数

¥Query params

布局在导航时不会重新渲染,因此它们无法访问搜索参数,否则这些参数会变得过时。

¥Layouts do not rerender on navigation, so they cannot access search params which would otherwise become stale.

要访问更新的查询参数,你可以使用 Page searchParams 属性,或使用 useSearchParams 钩子在客户端组件中读取它们。由于客户端组件在导航时重新渲染,因此它们可以访问最新的查询参数。

¥To access updated query parameters, you can use the Page searchParams prop, or read them inside a Client Component using the useSearchParams hook. Since Client Components re-render on navigation, they have access to the latest query parameters.

tsx
'use client'

import { useSearchParams } from 'next/navigation'

export default function Search() {
  const searchParams = useSearchParams()

  const search = searchParams.get('search')

  return '...'
}
tsx
import Search from '@/app/ui/search'

export default function Layout({ children }) {
  return (
    <>
      <Search />
      {children}
    </>
  )
}

路径名

¥Pathname

布局在导航时不会重新渲染,因此它们无法访问路径名,否则这些路径名会变得过时。

¥Layouts do not re-render on navigation, so they do not access pathname which would otherwise become stale.

要访问当前路径名,你可以使用 usePathname 钩子在客户端组件中读取它。由于客户端组件在导航期间重新渲染,因此它们可以访问最新的路径名。

¥To access the current pathname, you can read it inside a Client Component using the usePathname hook. Since Client Components re-render during navigation, they have access to the latest pathname.

tsx
'use client'

import { usePathname } from 'next/navigation'

// Simplified breadcrumbs logic
export default function Breadcrumbs() {
  const pathname = usePathname()
  const segments = pathname.split('/')

  return (
    <nav>
      {segments.map((segment, index) => (
        <span key={index}>
          {' > '}
          {segment}
        </span>
      ))}
    </nav>
  )
}
tsx
import { Breadcrumbs } from '@/app/ui/Breadcrumbs'

export default function Layout({ children }) {
  return (
    <>
      <Breadcrumbs />
      <main>{children}</main>
    </>
  )
}

获取数据

¥Fetching Data

布局无法将数据传递给其 children。但是,你可以在路由中多次获取相同的数据,并使用 React cache 来重复请求,而不会影响性能。

¥Layouts cannot pass data to their children. However, you can fetch the same data in a route more than once, and use React cache to dedupe the requests without affecting performance.

或者,在 Next.js 中使用 fetch 时,请求会自动进行数据去重。

¥Alternatively, when using fetchin Next.js, requests are automatically deduped.

tsx
export async function getUser(id: string) {
  const res = await fetch(`https://.../users/${id}`)
  return res.json()
}
tsx
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'

export default async function Layout({ children }) {
  const user = await getUser('1')

  return (
    <>
      <nav>
        {/* ... */}
        <UserName user={user.name} />
      </nav>
      {children}
    </>
  )
}
tsx
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'

export default async function Page() {
  const user = await getUser('1')

  return (
    <div>
      <h1>Welcome {user.name}</h1>
    </div>
  )
}

访问子段

¥Accessing child segments

布局无法访问其自身下方的路由段。要访问所有路由段,你可以在客户端组件中使用 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.

tsx
'use client'

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

export default function NavLink({
  slug,
  children,
}: {
  slug: string
  children: React.ReactNode
}) {
  const segment = useSelectedLayoutSegment()
  const isActive = slug === segment

  return (
    <Link
      href={`/blog/${slug}`}
      // Change style depending on whether the link is active
      style={{ fontWeight: isActive ? 'bold' : 'normal' }}
    >
      {children}
    </Link>
  )
}
tsx
import { NavLink } from './nav-link'
import getPosts from './get-posts'

export default async function Layout({
  children,
}: {
  children: React.ReactNode
}) {
  const featuredPosts = await getPosts()
  return (
    <div>
      {featuredPosts.map((post) => (
        <div key={post.id}>
          <NavLink slug={post.slug}>{post.title}</NavLink>
        </div>
      ))}
      <div>{children}</div>
    </div>
  )
}

示例

¥Examples

元数据

¥Metadata

你可以使用 metadata 对象generateMetadata 功能 修改 <head> HTML 元素(例如 titlemeta)。

¥You can modify the <head> HTML elements such as title and meta using the metadata object or generateMetadata function.

tsx
import type { Metadata } from 'next'

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

export default function Layout({ children }: { children: React.ReactNode }) {
  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 APIs which automatically handles advanced requirements such as streaming and de-duplicating <head> elements.

¥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:

tsx
'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>
  )
}
tsx
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>
  )
}

基于 params 显示内容

¥Displaying content based on params

使用 动态路由段,你可以根据 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>
  )
}

在客户端组件中读取 params

¥Reading params in Client Components

要在客户端组件(不能是 async)中使用 params,你可以使用 React 的 use 函数来读取 promise:

¥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)
}

版本历史

¥Version History

版本更改
v15.0.0-RCparams 现在是一个 promise。codemod 可用。
v13.0.0layout 已引入。