Файлы роутинга
Как работать со страницами и другими файлами роутинга Next.js App Router.
Назначение
src/app/** — точка входа приложения и слой файлового роутинга Next.js.
Файлы роутинга не реализуют интерфейс. Они описывают маршрут: читают параметры, получают данные первого рендера, подготавливают кеш или состояние и передают результат в screen.
Границы слоя описаны в Архитектура → Слои → App.
Граница ответственности
| Область | Где живёт |
|---|---|
Файлы маршрутов (page.tsx, layout.tsx, loading.tsx, error.tsx, not-found.tsx) | src/app/** |
Параметры маршрута, metadata, redirect(), notFound() | src/app/** |
| Серверные запросы для первого рендера | src/app/**, через готовые клиенты и сервисы нижних слоёв |
| Прогрев SWR-кеша, начальное состояние, подключение провайдеров | src/app/**, только через готовые обёртки из нижних слоёв |
| UI страницы | screens/ |
| Каркас страницы: header, footer, sidebar | layouts/ |
| Провайдеры, сторы, хуки, API-клиенты, сервисы | нижние слои (screens/, business/, infrastructure/, shared/) |
| CSS Modules и стили компонентов | рядом с компонентами, не в src/app/** |
Что можно делать в page.tsx
- Экспортировать
metadataилиgenerateMetadata. - Читать
paramsиsearchParams. - Нормализовать и валидировать параметры маршрута.
- Делать серверные запросы для первого рендера через готовые клиенты или сервисы.
- Вызывать
redirect()иnotFound(). - Готовить начальные данные для screen.
- Готовить SWR
fallbackи передавать его в готовый провайдер. - Подключать готовый провайдер стора страницы и передавать начальное состояние.
- Рендерить screen или композицию из готовых обёрток и screen.
Что запрещено
- Писать UI-разметку страницы прямо в файле роутинга.
- Создавать локальные компоненты внутри
src/app/**. - Добавлять CSS Modules, стили компонентов,
components/,styles/,hooks/,stores/,services/внутриsrc/app/**. - Реализовывать провайдеры, сторы, хуки, API-клиенты или сервисы в файлах роутинга.
- Размещать бизнес-логику, мапперы и правила предметной области в файлах роутинга.
- Вызывать
useSWRи доменные клиентские хуки в файлах роутинга.
Страницы
Страница объявляется через export default function. Для серверных запросов используется async function.
import type { Metadata } from 'next'
import { ProfileScreen } from 'screens/profile'
export const metadata: Metadata = {
title: 'Профиль',
description: 'Страница профиля пользователя',
}
type ProfilePageProps = {
params: Promise<{ id: string }>
}
export default async function ProfilePage({ params }: ProfilePageProps) {
const { id } = await params
return <ProfileScreen id={id} />
}Данные первого рендера
Если данные нужны до первого рендера, page.tsx получает их на сервере и передаёт в screen. Сам запрос выполняется через готовый клиент или сервис нижнего слоя.
import { notFound } from 'next/navigation'
import { userApi } from 'infrastructure/backend-api'
import { UserScreen } from 'screens/user'
type UserPageProps = {
params: Promise<{ id: string }>
}
export default async function UserPage({ params }: UserPageProps) {
const { id } = await params
const user = await userApi.users.get(id)
if (!user) {
notFound()
}
return <UserScreen user={user} />
}Если данные нужны нескольким клиентским SWR-хукам, файл роутинга может обернуть дерево в SWRConfig и передать fallback. Запросы стартуют на сервере, а клиентские хуки получают готовые данные из кеша.
Ключи fallback должны совпадать с ключами внутри готовых SWR-хуков.
import type { ReactNode } from 'react'
import { SWRConfig } from 'swr'
import { backendApi } from 'infrastructure/backend-api'
type FeedLayoutProps = {
children: ReactNode
}
export default async function FeedLayout({ children }: FeedLayoutProps) {
const userPromise = backendApi.user.getCurrent()
const postsPromise = backendApi.posts.list()
return (
<SWRConfig
value={{
fallback: {
'/api/user': userPromise,
'/api/posts': postsPromise,
},
}}
>
{children}
</SWRConfig>
)
}Подробнее о серверных запросах и SWR-кеше: REST → Серверные компоненты, REST → Клиентские компоненты.
Инициализация состояния
Файл роутинга может подключить готовый провайдер стора страницы, если состояние зависит от маршрута или данных первого рендера. Реализация стора и провайдера не размещается в src/app/**.
import { ProfileScreen, ProfileStoreProvider } from 'screens/profile'
type ProfilePageProps = {
params: Promise<{ id: string }>
}
export default async function ProfilePage({ params }: ProfilePageProps) {
const { id } = await params
return (
<ProfileStoreProvider initialState={{ userId: id }}>
<ProfileScreen />
</ProfileStoreProvider>
)
}Layout
layout.tsx подключает готовую инициализацию приложения: глобальные стили, провайдеры и верхнеуровневые обёртки из нижних слоёв.
Вёрстка layout-каркаса выносится в слой layouts/. Реализация провайдеров, стилей и UI не размещается в app/.
Error и Not Found
error.tsx и not-found.tsx делегируют разметку готовым screen или widget. В файле роутинга остаётся только адаптация API Next.js к пропсам нижнего слоя.
'use client'
import { ErrorScreen } from 'screens/error'
type ErrorPageProps = {
error: Error & { digest?: string }
reset: () => void
}
const ErrorPage = ({ error, reset }: ErrorPageProps) => {
return <ErrorScreen error={error} reset={reset} />
}
export default ErrorPage