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 将填充布局所包裹的路由段。这些主要将是子 布局(如果存在)或 页面 的组件,但在适用情况下,也可能是其他特殊文件,如 Loading 或 Error。
params(可选)一个 promise,它解析为一个对象,该对象包含从根路由段到该布局的 动态路由参数 对象。
export default async function Layout({
children,
params,
}: {
children: React.ReactNode
params: Promise<{ team: string }>
}) {
const { team } = await params
}export default async function Layout({ children, 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 函数来访问这些值。
params 是一个同步属性。为了向后兼容,您仍然可以在 Next.js 15 中同步访问它,但此行为将来会被废弃。您可以使用 LayoutProps 为布局添加类型,从而获得强类型的 params 和从目录结构推断出的命名槽。LayoutProps 是一个全局可用的辅助器。
export default function Layout(props: LayoutProps<'/dashboard'>) {
return (
<section>
{props.children}
{/* If you have app/dashboard/@analytics, it appears as a typed slot: */}
{/* {props.analytics} */}
</section>
)
}须知:
- 类型在
next dev、next build或next typegen期间生成。- 类型生成后,
LayoutProps辅助器是全局可用的。它无需导入。
app 目录必须包含一个根布局,它是 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>)添加到根布局中。相反,您应该使用 Metadata API,它会自动处理高级要求,例如流式传输和 <head> 元素的去重。app/(shop)/layout.js 的 /cart 导航到使用 app/(marketing)/layout.js 的 /blog 将导致完全页面加载。这仅适用于多个根布局。app/[lang]/layout.js 实现 国际化 时。布局在客户端导航期间被缓存,以避免不必要的服务器请求。
布局 不会重新渲染。它们可以被缓存和重用,以避免在页面之间导航时进行不必要的计算。通过限制布局访问原始请求,Next.js 可以防止在布局中执行可能缓慢或昂贵的用户代码,这可能会对性能产生负面影响。
要访问请求对象,您可以在 服务器组件 和函数中使用 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 '...'
}布局在导航时不会重新渲染,因此它们无法访问可能变得陈旧的搜索参数。
要访问更新的查询参数,您可以使用 Page searchParams 属性,或者在客户端组件中使用 useSearchParams Hook 读取它们。由于客户端组件在导航时会重新渲染,因此它们可以访问最新的查询参数。
'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 Hook 读取它。由于客户端组件在导航期间会重新渲染,因此它们可以访问最新的路径名。
'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>
)
}'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>
)
}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。
'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>
)
}'use client'
import Link from 'next/link'
import { useSelectedLayoutSegment } from 'next/navigation'
export default function NavLinks({ slug, children }) {
const segment = useSelectedLayoutSegment()
const isActive = slug === segment
return (
<Link
href={`/blog/${slug}`}
style={{ fontWeight: isActive ? 'bold' : 'normal' }}
>
{children}
</Link>
)
}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>)添加到根布局中。相反,请使用 Metadata API,它会自动处理高级要求,例如流式传输和<head>元素的去重。
您可以使用 usePathname Hook 来判断导航链接是否活跃。
由于 usePathname 是一个客户端 Hook,您需要将导航链接提取到客户端组件中,然后将其导入您的布局:
'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>
)
}'use client'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
export function Links() {
const pathname = usePathname()
return (
<nav>
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
Home
</Link>
<Link
className={`link ${pathname === '/about' ? 'active' : ''}`}
href="/about"
>
About
</Link>
</nav>
)
}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 显示内容使用 动态路由段,您可以根据 params 属性显示或获取特定内容。
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>
)
}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>
)
}params要在客户端组件(不能是 async)中使用 params,您可以使用 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。 |