Роутинг
Каждая папка в app/ — это сегмент URL, а специальные файлы определяют, что показывать.
app/
├── page.tsx # → /
├── about/
│ └── page.tsx # → /about
└── blog/
└── [slug]/
└── page.tsx # → /blog/hello-world
ПОЛНЫЙ СПИСОК СПЕЦИАЛЬНЫХ ФАЙЛОВ
- page.tsx - Страница (UI)
- layout.tsx - Обертка для страниц, сохраняется при навигации
- template.tsx - Как layout, но создается заново при навигации
- loading.tsx - UI загрузки (Suspense)
- error.tsx - UI ошибки (Error Boundary)
- not-found.tsx - UI 404
- route.ts - API эндпоинт
- middleware.ts - Перехват запросов (в корне)
ТИПЫ МАРШРУТОВ
- Статические маршруты
app/ ├── page.tsx # / ├── about/page.tsx # /about └── contact/page.tsx # /contact - Динамические маршруты
# Одиночный параметр app/blog/[slug]/page.tsx # /blog/hello-world # params: { slug: 'hello-world' } # Несколько параметров app/category/[category]/[id]/page.tsx # /category/phones/123 # params: { category: 'phones', id: '123' } - Catch-all маршруты ([...slug])
app/docs/[...slug]/page.tsx # /docs/getting-started/installation # params: { slug: ['getting-started', 'installation'] } - Optional catch-all ([[...slug]])
app/docs/[[...slug]]/page.tsx # /docs → params.slug = undefined # /docs/getting-started → params.slug = ['getting-started'] - Группы маршрутов ((folder))
app/(marketing)/page.tsx # / (без /marketing) app/(shop)/products/page.tsx # /products (без /shop) # Скобки НЕ влияют на URL, только на организацию - Параллельные маршруты (@folder)
app/@auth/login/page.tsx # Модалка логина app/@dashboard/page.tsx # Дашборд # Рендерятся одновременно - Перехватывающие маршруты ((..)folder)
app/(.)photos/[id]/page.tsx # Перехватывает /photos/123 # Аналогично относительным путям в файловой системе
НАВИГАЦИЯ
Компонент Link (предпочтительный способ)
123456789101112131415161718import Link from 'next/link';export default function Navigation() {return (<nav><Link href="/">Главная</Link><Link href="/about">О нас</Link><Link href="/products/123">Товар</Link><Link href="/blog/post-1">Статья</Link>{/* С заменой истории */}<Link href="/dashboard" replace>Дашборд (без добавления в историю)</Link>{/* С prefetch (по умолчанию true в production) */}<Link href="/heavy-page" prefetch={false}>Тяжелая страница</Link></nav>);}
Хук useRouter (для программной навигации)
1234567891011121314151617'use client';import { useRouter } from 'next/navigation';export default function NavigationButtons() {const router = useRouter();return (<div><button onClick={() => router.push('/about')}>О нас</button><button onClick={() => router.replace('/')}>На главную (без истории)</button><button onClick={() => router.back()}>Назад</button><button onClick={() => router.forward()}>Вперед</button><button onClick={() => router.refresh()}>Обновить данные</button><button onClick={() => router.prefetch('/heavy')}>Предзагрузить</button></div>);}
ПОЛУЧЕНИЕ ПАРАМЕТРОВ
В серверном компоненте
123456789101112131415161718// app/products/[category]/[id]/page.tsxexport default async function ProductPage({params,searchParams}: {params: { category: string; id: string };searchParams: { [key: string]: string | string[] | undefined };}) {// params из динамического маршрутаconsole.log(params.category); // 'phones'console.log(params.id); // '123'// searchParams из query-строкиconsole.log(searchParams.sort); // 'price'console.log(searchParams.page); // '2'return <div>Product</div>;}
На клиенте
12345678910111213'use client';import { useParams, useSearchParams, usePathname } from 'next/navigation';export default function ClientComponent() {const params = useParams(); // { category: 'phones', id: '123' }const searchParams = useSearchParams(); // URLSearchParams объектconst pathname = usePathname(); // '/products/phones/123'const sort = searchParams.get('sort'); // 'price'const page = searchParams.get('page'); // '2'return <div>...</div>;}