Menu

layout.js

布局 (layout) 是在路由之间共享的 UI。

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

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

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

属性

children (必需)

布局组件应该接受并使用 children 属性。在渲染过程中,children 将被填充为布局所包装的路由片段。这些主要是子 布局 (如果存在) 或 页面 的组件,但在适用的情况下也可能是其他特殊文件,如 LoadingError

params (可选)

从根段到该布局的 动态路由参数 对象。

示例URLparams
app/dashboard/[team]/layout.js/dashboard/1{ team: '1' }
app/shop/[tag]/[item]/layout.js/shop/1/2{ tag: '1', item: '2' }
app/blog/[...slug]/layout.js/blog/1/2{ slug: ['1', '2'] }

例如:

app/shop/[tag]/[item]/layout.tsx
export default function ShopLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: {
    tag: string;
    item: string;
  };
}) {
  // URL -> /shop/shoes/nike-air-max-97
  // `params` -> { tag: 'shoes', item: 'nike-air-max-97' }
  return <section>{children}</section>;
}
app/shop/[tag]/[item]/layout.js
export default function ShopLayout({ children, params }) {
  // URL -> /shop/shoes/nike-air-max-97
  // `params` -> { tag: 'shoes', item: 'nike-air-max-97' }
  return <section>{children}</section>;
}

注意事项

根布局

  • app 目录必须包含一个根 app/layout.js
  • 根布局必须定义 <html><body> 标签。
    • 不应手动添加 <head> 标签,如 <title><meta> 到根布局。相反,你应该使用 元数据 API,它会自动处理高级需求,如流式传输和去重 <head> 元素。
  • 你可以使用 路由组 来创建多个根布局。
    • 多个根布局之间导航将导致完整的页面加载 (而不是客户端导航)。例如,从使用 app/(shop)/layout.js/cart 导航到使用 app/(marketing)/layout.js/blog 将导致完整的页面加载。这适用于多个根布局。

布局不接收 searchParams

页面 不同,布局组件接收 searchParams 属性。这是因为共享布局在导航期间 不会重新渲染,这可能导致在导航之间出现过时的 searchParams

当使用客户端导航时,Next.js 会自动只渲染两个路由之间共同布局下面的页面部分。

例如,在以下目录结构中,dashboard/layout.tsx/dashboard/settings/dashboard/analytics 的共同布局:

文件结构显示一个 dashboard 文件夹嵌套了一个 layout.tsx 文件,以及 settings 和 analytics 文件夹,每个文件夹都有自己的页面

当从 /dashboard/settings 导航到 /dashboard/analytics 时,/dashboard/analytics 中的 page.tsx 将在服务器上重新渲染,而 dashboard/layout.tsx不会重新渲染,因为它是两个路由之间共享的 UI。

这种性能优化允许共享布局的页面之间的导航更快,因为只需要运行页面的数据获取和渲染,而不是整个路由,其中可能包括获取自己数据的共享布局。

因为 dashboard/layout.tsx 不会重新渲染,布局服务器组件中的 searchParams 属性在导航后可能变得过时

相反,使用页面的 searchParams 属性或在客户端组件中使用 useSearchParams hook,它会在客户端使用最新的 searchParams 重新渲染。

布局无法访问 pathname

布局无法访问 pathname。这是因为布局默认是服务器组件,并且 在客户端导航期间不会重新渲染,这可能导致 pathname 在导航之间变得过时。为了防止过时,Next.js 需要重新获取路由的所有段,从而失去缓存的好处并增加导航时的 RSC 负载 大小。

相反,你可以将依赖于 pathname 的逻辑提取到一个客户端组件中,并将其导入到你的布局中。由于客户端组件在导航期间会重新渲染 (但不会重新获取),你可以使用 Next.js hook,如 usePathname 来访问当前 pathname 并防止过时。

app/dashboard/layout.tsx
import { ClientComponent } from '@/app/ui/ClientComponent'
 
export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <ClientComponent />
      {/* 其他布局 UI */}
      <main>{children}</main>
    </>
  )
}
app/dashboard/layout.js
import { ClientComponent } from '@/app/ui/ClientComponent'
 
export default function Layout({ children }) {
  return (
    <>
      <ClientComponent />
      {/* 其他布局 UI */}
      <main>{children}</main>
    </>
  )
}

常见的 pathname 模式也可以通过 params 属性实现。

查看 示例 部分以获取更多信息。

版本历史

版本变更
v13.0.0引入了 layout