Современные высоконагруженные веб-приложения требуют сложных и динамических сценариев отображения интерфейса:
loading.tsx) и изолированно обрабатывать сбои (error.tsx), не нарушая работу соседних панелей./photo/123). При этом прямой заход по этой же ссылке или обновление страницы по F5 должны открывать полноценную отдельную страницу, а не модалку.Next.js App Router решает эти задачи на уровне ядра с помощью параллельных маршрутов (Parallel Routes) и перехватывающих маршрутов (Intercepting Routes).
@slot)#Параллельные маршруты создаются с использованием папок с префиксом @ (слотов). Имена слотов не попадают в публичный URL (app/dashboard/@analytics компилируется в URL /dashboard), а передаются как именованные React-пропсы в компонент layout.tsx соответствующего уровня.
Основной файл page.tsx неявно является слотом по умолчанию children (эквивалентен @children). Каждый параллельный слот представляет собой независимую ветку React-дерева со своим жизненным циклом, границей Suspense и кэшем.
(.), (..))#Перехватывающие маршруты позволяют замапить клиентский переход по ссылке на альтернативный компонент, сохраняя контекст текущего экрана:
(.) — перехват маршрута на том же уровне сегмента.(..) — перехват маршрута на один сегмент выше.(..)(..) — перехват на два сегмента выше.(...) — перехват от самого корня директории app.Важно: Папки параллельных слотов (
@slot) и группы маршрутов ((group)) не являются сегментами URL и полностью игнорируются при подсчете уровней для(..).
Пользователь находится на /feed и кликает <Link href='/photo/42' scroll={false}>.
Turbopack определяет перехватчик app/feed/@modal/(..)photo/[id]/page.tsx вместо стандартной страницы.
Layout рендерит фоновый children (/feed) и внедряет модалку в слот @modal. URL меняется на /photo/42.
Пользователь нажимает F5 или переходит по ссылке напрямую из мессенджера.
Сервер рендерит полноэкранный app/photo/[id]/page.tsx. Фоновый слот использует default.tsx (null), избегая ошибки 404.
Что произойдет, если пользователь нажмет F5 на странице с открытой через @modal карточкой /photo/42, но в директории слота отсутствует файл default.tsx?
Параллельные слоты передаются в layout.tsx как именованные React-пропсы:
interface DashboardLayoutProps {
children: React.ReactNode;
analytics: React.ReactNode; // Слот app/dashboard/@analytics
notifications: React.ReactNode; // Слот app/dashboard/@notifications
}
export default function DashboardLayout({
children,
analytics,
notifications,
}: DashboardLayoutProps) {
return (
<div className="p-6 space-y-6">
<main>{children}</main>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<section className="border border-border p-4 rounded-lg">
{analytics}
</section>
<section className="border border-border p-4 rounded-lg">
{notifications}
</section>
</div>
</div>
);
}Файл default.tsx определяет, что отображать в параллельном слоте, когда текущий URL не совпадает ни с одним маршрутом внутри этого слота:
// Если модальное окно не активно, возвращаем null для скрытия слота
export default function DefaultModalSlot() {
return null;
}У вас есть структура app/feed/@modal/(..)photo/[id]/page.tsx. Сколько уровней URL перехватывает оператор (..) в данном случае?
'use client';
import { useRouter } from 'next/navigation';
import { use } from 'react';
interface PhotoModalProps {
params: Promise<{ id: string }>;
}
export default function PhotoModal({ params }: PhotoModalProps) {
const router = useRouter();
const { id } = use(params);
return (
<div className="fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-4">
<div className="bg-bg-surface border border-border rounded-xl max-w-lg w-full p-6 space-y-4">
<div className="flex justify-between items-center">
<h3 className="text-lg font-bold">Фото #{id}</h3>
{/* router.back() восстанавливает предыдущий URL без модалки */}
<button
onClick={() => router.back()}
className="text-text-muted hover:text-text-primary text-sm px-2 py-1 rounded bg-bg-elevated"
>
Закрыть ✕
</button>
</div>
<div className="aspect-video bg-black/40 rounded-lg flex items-center justify-center font-mono text-xs text-text-muted">
[Просмотр фотографии {id}]
</div>
</div>
</div>
);
}Если вы используете параллельные маршруты (@slot), отсутствие файла default.tsx в корне слота приведет к тому, что при первом же обновлении страницы клавишей F5 всё приложение упадет с ошибкой 404.
Как избежать: Всегда создавайте файл default.tsx (хотя бы возвращающий return null;) в каждом объявленном слоте.
Разработчики часто пишуят (..)(..)photo, ошибочно полагая, что @modal считается за отдельный сегмент URL. В результате перехватчик не срабатывает.
Как избежать: Запомните: папки со знаком @ и круглыми скобками () прозрачны для URL-маршрутизатора. Для выхода из app/feed/@modal на уровень app/ требуется ровно один оператор (..).
При переходе по ссылке <Link href="/photo/123"> браузер по умолчанию прокручивает страницу в самый верх, сбивая положение фонового списка.
Как избежать: Всегда указывайте флаг scroll={false} при вызове ссылок, открывающих контекстные модальные окна: <Link href="/photo/123" scroll={false}>.