layout.js 文件
layout
文件用于在 Next.js 应用程序中定义布局。
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return <section>{children}</section>
}
export default function DashboardLayout({ children }) {
return <section>{children}</section>
}
根布局 (root layout) 是根 app
目录中的最顶层布局,用于定义 <html>
和 <body>
标签以及其他全局共享的 UI。
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
参考
属性
children
(必填)
布局组件应接受并使用 children
属性。在渲染过程中,children
将被填充为该布局所包裹的路由段。这些主要是子 布局 (Layout) 或 页面 (Page) 的组件,但也可能是其他特殊文件,如 加载 (Loading) 或 错误 (Error) 文件(如果适用)。
params
(可选)
一个解析为包含从根段到该布局的 动态路由参数 (dynamic route parameters) 对象的 Promise。
export default async function Layout({
params,
}: {
params: Promise<{ team: string }>
}) {
const { team } = await params
}
export default async function Layout({ params }) {
const { team } = await params
}
示例路由 | URL | params |
---|---|---|
app/dashboard/[team]/layout.js | /dashboard/1 | Promise<{ team: '1' }> |
app/shop/[tag]/[item]/layout.js | /shop/1/2 | Promise<{ tag: '1', item: '2' }> |
app/blog/[...slug]/layout.js | /blog/1/2 | Promise<{ slug: ['1', '2'] }> |
- 由于
params
属性是一个 Promise,必须使用async/await
或 React 的use
函数来访问其值。- 在版本 14 及更早版本中,
params
是一个同步属性。为了向后兼容,在 Next.js 15 中仍然可以同步访问它,但此行为将在未来被弃用。
- 在版本 14 及更早版本中,
根布局
app
目录 必须 包含一个根 app/layout.js
文件。
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html>
<body>{children}</body>
</html>
)
}
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
</html>
)
}
- 根布局 必须 定义
<html>
和<body>
标签。- 不应手动向根布局添加
<head>
标签(如<title>
和<meta>
)。而应使用 元数据 API (Metadata API),它会自动处理高级需求,如流式传输和去重<head>
元素。
- 不应手动向根布局添加
- 可以使用 路由组 (route groups) 创建多个根布局。
- 在 多个根布局之间 导航会导致 完整页面加载(而不是客户端导航)。例如,从使用
app/(shop)/layout.js
的/cart
导航到使用app/(marketing)/layout.js
的/blog
将导致完整页面加载。这 仅 适用于多个根布局。
- 在 多个根布局之间 导航会导致 完整页面加载(而不是客户端导航)。例如,从使用
注意事项
请求对象
布局在导航期间会被缓存在客户端,以避免不必要的服务器请求。
布局 (Layouts) 不会重新渲染。它们可以被缓存和重用,以避免在页面之间导航时进行不必要的计算。通过限制布局访问原始请求,Next.js 可以防止在布局中执行可能缓慢或昂贵的用户代码,从而影响性能。
要访问请求对象,可以在 服务器组件 (Server Components) 和函数中使用 headers
和 cookies
API。
import { cookies } from 'next/headers'
export default async function Layout({ children }) {
const cookieStore = await cookies()
const theme = cookieStore.get('theme')
return '...'
}
import { cookies } from 'next/headers'
export default async function Layout({ children }) {
const cookieStore = await cookies()
const theme = cookieStore.get('theme')
return '...'
}
查询参数
布局在导航时不会重新渲染,因此无法访问可能过期的搜索参数。
要访问更新的查询参数,可以使用页面 searchParams
属性,或在客户端组件中使用 useSearchParams
钩子。由于客户端组件在导航时会重新渲染,它们可以访问最新的查询参数。
'use client'
import { useSearchParams } from 'next/navigation'
export default function Search() {
const searchParams = useSearchParams()
const search = searchParams.get('search')
return '...'
}
'use client'
import { useSearchParams } from 'next/navigation'
export default function Search() {
const searchParams = useSearchParams()
const search = searchParams.get('search')
return '...'
}
import Search from '@/app/ui/search'
export default function Layout({ children }) {
return (
<>
<Search />
{children}
</>
)
}
import Search from '@/app/ui/search'
export default function Layout({ children }) {
return (
<>
<Search />
{children}
</>
)
}
路径名
布局在导航时不会重新渲染,因此无法访问可能过期的路径名。
要访问当前路径名,可以在客户端组件中使用 usePathname
钩子。由于客户端组件在导航时会重新渲染,它们可以访问最新的路径名。
import { Breadcrumbs } from '@/app/ui/Breadcrumbs'
export default function Layout({ children }) {
return (
<>
<Breadcrumbs />
<main>{children}</main>
</>
)
}
import { Breadcrumbs } from '@/app/ui/Breadcrumbs'
export default function Layout({ children }) {
return (
<>
<Breadcrumbs />
<main>{children}</main>
</>
)
}
数据获取
布局无法将数据传递给其 children
。但是,可以在路由中多次获取相同的数据,并使用 React 的 cache
来去重请求而不影响性能。
或者,在使用 Next.js 的 fetch
时,请求会自动去重。
export async function getUser(id: string) {
const res = await fetch(`https://.../users/${id}`)
return res.json()
}
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}
</>
)
}
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}
</>
)
}
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>
)
}
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>
)
}
访问子路由段
布局无法访问其下方的路由段。要访问所有路由段,可以在客户端组件中使用 useSelectedLayoutSegment
或 useSelectedLayoutSegments
。
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>
)
}
import { NavLink } from './nav-link'
import getPosts from './get-posts'
export default async function Layout({ children }) {
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 元素,如 title
和 meta
。
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Next.js',
}
export default function Layout({ children }: { children: React.ReactNode }) {
return '...'
}
export const metadata = {
title: 'Next.js',
}
export default function Layout({ children }) {
return '...'
}
须知:不应手动向根布局添加
<head>
标签(如<title>
和<meta>
)。而应使用 元数据 API (Metadata APIs),它会自动处理高级需求,如流式传输和去重<head>
元素。
活跃导航链接
可以使用 usePathname
钩子来确定导航链接是否活跃。
由于 usePathname
是一个客户端钩子,需要将导航链接提取到一个客户端组件中,然后可以将其导入到布局中:
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>
)
}
基于 params
显示内容
使用动态路由段 (dynamic route segments),您可以根据 params
属性显示或获取特定内容。
export default async function DashboardLayout({
children,
params,
}: {
children: React.ReactNode
params: Promise<{ team: string }>
}) {
const { team } = await params
return (
<section>
<header>
<h1>欢迎来到 {team} 的控制面板</h1>
</header>
<main>{children}</main>
</section>
)
}
export default async function DashboardLayout({ children, params }) {
const { team } = await params
return (
<section>
<header>
<h1>欢迎来到 {team} 的控制面板</h1>
</header>
<main>{children}</main>
</section>
)
}
在客户端组件中读取 params
要在客户端组件 (Client Component) 中使用 params
(客户端组件不能是 async
的),您可以使用 React 的 use
函数来读取 promise:
'use client'
import { use } from 'react'
export default function Page({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = use(params)
}
'use client'
import { use } from 'react'
export default function Page({ params }) {
const { slug } = use(params)
}
版本历史
版本 | 变更 |
---|---|
v15.0.0-RC | params 现在是一个 promise。提供了代码迁移工具 (codemod)。 |
v13.0.0 | 引入 layout 。 |