Параллельные маршруты (Parallel Routes)

Параллельная маршрутизация позволяет одновременно или условно рендерить одну или несколько страниц в одном лейауте. Для высокодинамичных разделов приложений, таких как дашборды и ленты в соцсетях, параллельная маршрутизация может использоваться для реализации сложных паттернов навигации.

Например, вы можете одновременно рендерить страницы команды и аналитики.

Диаграмма параллельных маршрутов

Параллельная маршрутизация позволяет определять независимые состояния ошибок и загрузки для каждого маршрута, так как они загружаются независимо.

Параллельные маршруты позволяют использовать кастомные состояния ошибок и загрузки

Параллельная маршрутизация также позволяет условно рендерить слот на основе определенных условий, таких как состояние аутентификации. Это позволяет полностью разделять код для одного URL.

Диаграмма условных маршрутов

Конвенция

Параллельные маршруты создаются с использованием именованных слотов. Слоты определяются с помощью соглашения @folder и передаются в лейаут того же уровня в качестве пропсов.

Слоты не являются сегментами маршрута и не влияют на структуру URL. Путь /@team/members будет доступен по адресу /members.

Например, следующая структура файлов определяет два явных слота: @analytics и @team.

Структура файловой системы параллельных маршрутов

Приведенная структура папок означает, что компонент в app/layout.js теперь принимает пропсы слотов @analytics и @team и может рендерить их параллельно вместе с пропсом children:

export default function Layout(props: {
  children: React.ReactNode
  analytics: React.ReactNode
  team: React.ReactNode
}) {
  return (
    <>
      {props.children}
      {props.team}
      {props.analytics}
    </>
  )
}
export default function Layout(props) {
  return (
    <>
      {props.children}
      {props.team}
      {props.analytics}
    </>
  )
}

Полезно знать: Проп children является неявным слотом, который не нужно сопоставлять с папкой. Это означает, что app/page.js эквивалентен app/@children/page.js.

Несоответствующие маршруты

По умолчанию содержимое, рендерящееся внутри слота, будет соответствовать текущему URL.

В случае несоответствующего слота, содержимое, которое рендерит Next.js, отличается в зависимости от техники маршрутизации и структуры папок.

default.js

Вы можете определить файл default.js для рендеринга в качестве резервного варианта, когда Next.js не может восстановить активное состояние слота на основе текущего URL.

Рассмотрим следующую структуру папок. Слот @team имеет директорию settings, а @analytics — нет.

Несоответствующие маршруты в параллельной маршрутизации

Навигация

При навигации Next.js будет рендерить предыдущее активное состояние слота, даже если оно не соответствует текущему URL.

Перезагрузка

При перезагрузке Next.js сначала попытается рендерить файл default.js несоответствующего слота. Если он недоступен, будет отображена ошибка 404.

Ошибка 404 для несоответствующих маршрутов помогает убедиться, что вы случайно не рендерите маршрут, который не должен быть параллельно отображен.

useSelectedLayoutSegment(s)

И useSelectedLayoutSegment, и useSelectedLayoutSegments принимают параметр parallelRoutesKey, который позволяет читать активный сегмент маршрута внутри этого слота.

'use client'

import { useSelectedLayoutSegment } from 'next/navigation'

export default async function Layout(props: {
  //...
  auth: React.ReactNode
}) {
  const loginSegments = useSelectedLayoutSegment('auth')
  // ...
}
'use client'

import { useSelectedLayoutSegment } from 'next/navigation'

export default async function Layout(props) {
  const loginSegments = useSelectedLayoutSegment('auth')
  // ...
}

Когда пользователь переходит на @auth/login или /login в адресной строке, loginSegments будет равен строке "login".

Примеры

Модальные окна

Параллельная маршрутизация может использоваться для рендеринга модальных окон.

Диаграмма параллельных маршрутов

Слот @auth рендерит компонент <Modal>, который можно показать, перейдя на соответствующий маршрут, например /login.

export default async function Layout(props: {
  // ...
  auth: React.ReactNode
}) {
  return (
    <>
      {/* ... */}
      {props.auth}
    </>
  )
}
export default async function Layout(props) {
  return (
    <>
      {/* ... */}
      {props.auth}
    </>
  )
}
import { Modal } from 'components/modal'

export default function Login() {
  return (
    <Modal>
      <h1>Вход</h1>
      {/* ... */}
    </Modal>
  )
}
import { Modal } from 'components/modal'

export default function Login() {
  return (
    <Modal>
      <h1>Вход</h1>
      {/* ... */}
    </Modal>
  )
}

Чтобы содержимое модального окна не рендерилось, когда оно неактивно, вы можете создать файл default.js, который возвращает null.

export default function Default() {
  return null
}
export default function Default() {
  return null
}

Закрытие модального окна

Если модальное окно было инициировано через клиентскую навигацию, например с помощью <Link href="/login">, вы можете закрыть его, вызвав router.back() или используя компонент Link.

'use client'
import { useRouter } from 'next/navigation'
import { Modal } from 'components/modal'

export default async function Login() {
  const router = useRouter()
  return (
    <Modal>
      <span onClick={() => router.back()}>Закрыть окно</span>
      <h1>Вход</h1>
      ...
    </Modal>
  )
}
'use client'
import { useRouter } from 'next/navigation'
import { Modal } from 'components/modal'

export default async function Login() {
  const router = useRouter()
  return (
    <Modal>
      <span onClick={() => router.back()}>Закрыть окно</span>
      <h1>Вход</h1>
      ...
    </Modal>
  )
}

Подробнее о модальных окнах рассказывается в разделе Перехватывающие маршруты.

Если вы хотите перейти в другое место и закрыть модальное окно, вы также можете использовать catch-all маршрут.

Диаграмма параллельных маршрутов
export default function CatchAll() {
  return null
}
export default function CatchAll() {
  return null
}

Catch-all маршруты имеют приоритет над default.js.

Условные маршруты

Параллельная маршрутизация может использоваться для реализации условной маршрутизации. Например, вы можете рендерить маршрут @dashboard или @login в зависимости от состояния аутентификации.

import { getUser } from '@/lib/auth'

export default function Layout({
  dashboard,
  login,
}: {
  dashboard: React.ReactNode
  login: React.ReactNode
}) {
  const isLoggedIn = getUser()
  return isLoggedIn ? dashboard : login
}
import { getUser } from '@/lib/auth'

export default function Layout({ dashboard, login }) {
  const isLoggedIn = getUser()
  return isLoggedIn ? dashboard : login
}
Пример аутентификации с параллельными маршрутами