Продвинутый роутинг: parallel и intercepting
Next.js App Router поддерживает сложные схемы роутинга: параллельные маршруты и перехватывающие.
Parallel Routes
Параллельные маршруты позволяют рендерить несколько страниц в одном layout одновременно. Обозначаются через @:
app/
├── @modal/
│ └── login/
│ └── page.tsx
├── @sidebar/
│ └── page.tsx
├── layout.tsx
└── page.tsx
Layout получает их как пропсы:
export default function Layout({
children,
modal,
sidebar,
}: {
children: React.ReactNode;
modal: React.ReactNode;
sidebar: React.ReactNode;
}) {
return (
<>
<aside>{sidebar}</aside>
<main>{children}</main>
{modal}
</>
);
}
Слоты со своими loading и error
Каждый слот имеет свой loading.tsx и error.tsx:
@modal/
├── login/
│ └── page.tsx
├── loading.tsx
└── error.tsx
Intercepting Routes
Перехватывающие маршруты показывают одну страницу в контексте другой. Обозначаются через (.), (..), (...):
app/
├── feed/
│ └── page.tsx
├── photo/
│ └── [id]/
│ └── page.tsx
└── (.)photo/
└── [id]/
└── page.tsx
(.)— тот же уровень(..)— уровень выше(..)(..)— два уровня выше(...)— из корняapp
Практика: модалка из URL
Классический сценарий: клик по фото в ленте открывает модалку, но URL меняется на /photo/123.
app/feed/page.tsx:
import Link from 'next/link';
export default function Feed() {
return (
<div>
{photos.map((photo) => (
<Link key={photo.id} href={`/photo/${photo.id}`}>
<img src={photo.thumbnail} alt="" />
</Link>
))}
</div>
);
}
app/@modal/(.)photo/[id]/page.tsx:
export default async function PhotoModal({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
return (
<div className="modal">
<img src={`/photos/${id}.jpg`} alt="" />
</div>
);
}
app/photo/[id]/page.tsx — полная страница, если открыть URL напрямую:
export default async function PhotoPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
return (
<div>
<img src={`/photos/${id}.jpg`} alt="" />
<p>Полная страница фото</p>
</div>
);
}
Закрытие модалки
Через router.back() или через отдельный маршрут, который делает редирект:
'use client';
import { useRouter } from 'next/navigation';
export default function Modal() {
const router = useRouter();
return (
<div className="modal" onClick={() => router.back()}>
{/* содержимое */}
</div>
);
}
Когда использовать
Parallel routes — когда нужно несколько независимых областей на одной странице: дашборд, sidebar, modal, чат.
Intercepting routes — когда нужны модалки с сохранением URL: галереи, детали из списка, авторизация.
Задание
- Создай слот
@modalв layout - Сделай перехватывающий маршрут для
/photo/[id] - Проверь: клик открывает модалку, прямой заход — полную страницу
- Добавь
router.back()для закрытия
Что дальше
Следующий курс: «Данные и кеширование» — загрузка данных, кеш, ревалидация.