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>
, а также других глобально используемых элементов интерфейса.
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) от корневого сегмента до этого макета.
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
является промисом, вы должны использоватьasync/await
или функцию Reactuse
для доступа к значениям.- В версии 14 и ранее
params
был синхронным пропсом. Для обеспечения обратной совместимости вы всё ещё можете получать к нему доступ синхронно в Next.js 15, но это поведение будет устаревшим в будущем.
- В версии 14 и ранее
Корневой макет (Root Layout)
Директория 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>
.
- Вручную добавлять теги
- Вы можете использовать группы маршрутов (route groups) для создания нескольких корневых макетов.
- Переход между несколькими корневыми макетами вызовет полную перезагрузку страницы (в отличие от клиентской навигации). Например, переход с
/cart
, который используетapp/(shop)/layout.js
, на/blog
, который используетapp/(marketing)/layout.js
, вызовет полную перезагрузку страницы. Это применимо только к нескольким корневым макетам.
- Переход между несколькими корневыми макетами вызовет полную перезагрузку страницы (в отличие от клиентской навигации). Например, переход с
Ограничения
Объект запроса
Макеты кэшируются на клиенте во время навигации, чтобы избежать ненужных запросов к серверу.
Макеты (Layouts) не перерендериваются. Они могут быть закэшированы и повторно использованы, чтобы избежать ненужных вычислений при переходе между страницами. Ограничивая доступ макетов к сырому запросу, Next.js предотвращает выполнение потенциально медленного или ресурсоёмкого пользовательского кода внутри макета, что может негативно повлиять на производительность.
Для доступа к объекту запроса вы можете использовать API headers
и cookies
в Серверных Компонентах (Server Components) и функциях.
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 '...'
}
Параметры запроса (Query params)
Макеты не перерендериваются при навигации, поэтому они не могут получить доступ к параметрам поиска, которые в противном случае стали бы устаревшими.
Для доступа к обновлённым параметрам запроса вы можете использовать пропс searchParams
Страницы (Page) или прочитать их внутри Клиентского Компонента с помощью хука 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}
</>
)
}
Путь (Pathname)
Макеты не перерендериваются при навигации, поэтому они не получают доступ к пути, который в противном случае стал бы устаревшим.
Для доступа к текущему пути вы можете прочитать его внутри Клиентского Компонента с помощью хука 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
для дедупликации запросов без влияния на производительность.
Альтернативно, при использовании fetch
в Next.js запросы автоматически дедуплицируются.
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>
)
}
Примеры
Метаданные
Вы можете изменять HTML-элементы <head>
, такие как title
и meta
, используя объект metadata
или функцию generateMetadata
.
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
для определения активности ссылки навигации.
Поскольку 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
Используя динамические сегменты маршрутов, вы можете отображать или получать конкретный контент на основе пропса 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
в клиентских компонентах
Для использования params
в клиентском компоненте (который не может быть async
), вы можете использовать функцию use
из React для чтения промиса:
'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 теперь является промисом. Доступен кодмод. |
v13.0.0 | Добавлен layout . |