Page-level компоненты
Специальные файлы Next.js App Router, которые фреймворк использует по соглашению: layout.tsx, page.tsx, loading.tsx, error.tsx, not-found.tsx, template.tsx.
Общие правила
- Экспорт через
export default function— конвенция Next.js. - Типизация через
PropsWithChildrenили явный интерфейс. - Каждая страница (
page.tsx) должна содержатьmetadataсtitleиdescription. - Минимум логики — page-level компоненты делегируют работу экранам, виджетам и фичам.
- Стили в page-level компонентах не используются — стилизация внутри вызываемых компонентов.
layout.tsx
Корневой layout — точка подключения провайдеров, глобальных стилей и метаданных.
import type { PropsWithChildren } from 'react'
import type { Metadata } from 'next'
import { Providers } from './providers'
import './styles/index.css'
export const metadata: Metadata = {
title: {
default: 'App',
template: '%s | App',
},
description: 'Описание приложения',
metadataBase: new URL('https://example.com'),
openGraph: {
type: 'website',
locale: 'ru_RU',
siteName: 'App',
images: [
{
url: '/og-image.png',
width: 1200,
height: 630,
alt: 'App',
},
],
},
twitter: {
card: 'summary_large_image',
},
}
export default function RootLayout({ children }: PropsWithChildren) {
return (
<html lang="ru" suppressHydrationWarning>
<body>
<Providers>
{children}
</Providers>
</body>
</html>
)
}Вложенный layout — для секции с общей обёрткой (sidebar, header):
import type { PropsWithChildren } from 'react'
import { DashboardLayout } from '@/shared/ui/dashboard-layout'
export default function Layout({ children }: PropsWithChildren) {
return (
<DashboardLayout>
{children}
</DashboardLayout>
)
}page.tsx
Тонкий файл — только импорт и рендер экрана. Логика, стили и зависимости размещаются в экране, не в page.tsx.
import type { Metadata } from 'next'
import { HomeScreen } from '@/screens/home'
export const metadata: Metadata = {
title: 'Главная',
description: 'Главная страница приложения',
}
export default function HomePage() {
return <HomeScreen />
}С параметрами маршрута:
import type { Metadata } from 'next'
import { ProfileScreen } from '@/screens/profile'
export const metadata: Metadata = {
title: 'Профиль',
description: 'Страница профиля пользователя',
}
interface ProfilePageProps {
params: Promise<{ id: string }>
}
export default async function ProfilePage({ params }: ProfilePageProps) {
const { id } = await params
return <ProfileScreen id={id} />
}Каждая страница должна содержать metadata с title — он подставится в шаблон из корневого layout: Профиль | App.
loading.tsx
Состояние загрузки. Показывается пока загружается контент страницы.
export default function Loading() {
return <div>Загрузка...</div>
}error.tsx
Обработка ошибок. Обязательно 'use client' — error boundary работает только на клиенте. Разметку выносим в экран.
'use client'
import type { FC } from 'react'
import { ErrorScreen } from '@/screens/error'
interface ErrorPageProps {
error: Error & { digest?: string }
reset: () => void
}
const ErrorPage: FC<ErrorPageProps> = ({ error, reset }) => {
return <ErrorScreen error={error} reset={reset} />
}
export default ErrorPagenot-found.tsx
Страница 404. Показывается когда маршрут не найден. Разметку выносим в экран.
import type { Metadata } from 'next'
import { NotFoundScreen } from '@/screens/not-found'
export const metadata: Metadata = {
title: 'Страница не найдена',
description: 'Запрашиваемая страница не существует',
}
export default function NotFound() {
return <NotFoundScreen />
}template.tsx
Аналог layout, но пересоздаётся при каждой навигации (не сохраняет состояние). Используется редко — для анимаций переходов между страницами.
import type { PropsWithChildren } from 'react'
export default function Template({ children }: PropsWithChildren) {
return <div>{children}</div>
}