跳到主要内容

布局(layout.js)

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

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

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

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

参考

Props

children (必需)

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

params (可选)

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

app/dashboard/[team]/layout.tsx
export default async function Layout({
children
params,
}: {
children: React.ReactNode
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 函数来访问值。
    • 在版本 14 及更早版本中,params 是一个同步 prop。为了帮助向后兼容,你仍然可以在 Next.js 15 中同步访问它,但此行为将在未来被弃用。

根布局

app 目录必须包含一个根布局,它是根 app 目录中最顶层的布局。通常,根布局是 app/layout.js

app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>{children}</body>
</html>
)
}
  • 根布局必须定义 <html><body> 标签。
    • 不应该手动向根布局添加 <head> 标签,如 <title><meta>。相反,你应该使用元数据 API,它会自动处理高级要求,如流式传输和去重 <head> 元素。
  • 你可以使用路由组来创建多个根布局
    • 多个根布局之间导航将导致完整页面加载(与客户端导航相反)。例如,从使用 app/(shop)/layout.js/cart 导航到使用 app/(marketing)/layout.js/blog 将导致完整页面加载。这适用于多个根布局。
  • 根布局可以在动态段下,例如在实现国际化时使用 app/[lang]/layout.js

注意事项

请求对象

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

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

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

app/shop/layout.tsx
import { cookies } from 'next/headers'

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

查询参数

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

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

app/ui/search.tsx
'use client'

import { useSearchParams } from 'next/navigation'

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

const search = searchParams.get('search')

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

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

路径名

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

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

app/ui/breadcrumbs.tsx
'use client'

import { usePathname } from 'next/navigation'

// 简化的面包屑逻辑
export default function Breadcrumbs() {
const pathname = usePathname()
const segments = pathname.split('/')

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

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

获取数据

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

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

app/lib/data.ts
export async function getUser(id: string) {
const res = await fetch(`https://.../users/${id}`)
return res.json()
}
app/dashboard/layout.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}
</>
)
}
app/dashboard/page.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>
)
}

访问子段

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

app/ui/nav-link.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}`}
// 根据链接是否激活来更改样式
style={{ fontWeight: isActive ? 'bold' : 'normal' }}
>
{children}
</Link>
)
}
app/blog/layout.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>
)
}

示例

元数据

你可以使用metadata 对象generateMetadata 函数修改 <head> HTML 元素,如 titlemeta

app/layout.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> 元素。

活动导航链接

你可以使用 usePathname 钩子来确定导航链接是否激活。

由于 usePathname 是一个客户端钩子,你需要将导航链接提取到客户端组件中,该组件可以导入到你的布局中:

app/ui/nav-links.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>
)
}
app/layout.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 显示内容

使用动态路由段,你可以基于 params prop 显示或获取特定内容。

app/dashboard/layout.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

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

app/page.tsx
'use client'

import { use } from 'react'

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

版本历史

版本变更
v15.0.0-RCparams 现在是 Promise。提供 codemod
v13.0.0引入 layout