Skip to content

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 — точка подключения провайдеров, глобальных стилей и метаданных.

tsx
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):

tsx
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.

tsx
import type { Metadata } from 'next'
import { HomeScreen } from '@/screens/home'

export const metadata: Metadata = {
  title: 'Главная',
  description: 'Главная страница приложения',
}

export default function HomePage() {
  return <HomeScreen />
}

С параметрами маршрута:

tsx
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

Состояние загрузки. Показывается пока загружается контент страницы.

tsx
export default function Loading() {
  return <div>Загрузка...</div>
}

error.tsx

Обработка ошибок. Обязательно 'use client' — error boundary работает только на клиенте. Разметку выносим в экран.

tsx
'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 ErrorPage

not-found.tsx

Страница 404. Показывается когда маршрут не найден. Разметку выносим в экран.

tsx
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, но пересоздаётся при каждой навигации (не сохраняет состояние). Используется редко — для анимаций переходов между страницами.

tsx
import type { PropsWithChildren } from 'react'

export default function Template({ children }: PropsWithChildren) {
  return <div>{children}</div>
}