Parallele Routen

Parallele Routen (Parallel Routes) ermöglichen es Ihnen, eine oder mehrere Seiten gleichzeitig oder bedingt innerhalb desselben Layouts zu rendern. Sie sind nützlich für hochdynamische Bereiche einer Anwendung, wie Dashboards und Feeds in sozialen Netzwerken.

Beispielsweise können Sie bei einem Dashboard parallele Routen verwenden, um die Seiten team und analytics gleichzeitig zu rendern:

Diagramm der parallelen Routen

Konvention

Slots

Parallele Routen werden mit benannten Slots erstellt. Slots werden mit der @folder-Konvention definiert. Die folgende Dateistruktur definiert beispielsweise zwei Slots: @analytics und @team:

Dateisystemstruktur für parallele Routen

Slots werden als Props an das übergeordnete Layout übergeben. Für das obige Beispiel akzeptiert die Komponente in app/layout.js nun die Slot-Props @analytics und @team und kann sie parallel zum children-Prop rendern:

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

Slots sind jedoch keine Routensegmente und beeinflussen die URL-Struktur nicht. Für /@analytics/views lautet die URL beispielsweise /views, da @analytics ein Slot ist. Slots werden mit der regulären Page-Komponente kombiniert, um die endgültige Seite zu bilden, die mit dem Routensegment verknüpft ist. Daher können Sie auf derselben Ebene des Routensegments keine separaten statischen und dynamischen Slots haben. Wenn ein Slot dynamisch ist, müssen alle Slots auf dieser Ebene dynamisch sein.

Gut zu wissen:

  • Das children-Prop ist ein impliziter Slot, der keinem Ordner zugeordnet werden muss. Das bedeutet, dass app/page.js äquivalent zu app/@children/page.js ist.

default.js

Sie können eine default.js-Datei definieren, die als Fallback für nicht übereinstimmende Slots während des initialen Ladens oder eines vollständigen Seitenneuaufbaus gerendert wird.

Betrachten Sie die folgende Ordnerstruktur. Der @team-Slot hat eine /settings-Seite, aber @analytics nicht.

Nicht übereinstimmende Routen bei parallelen Routen

Bei der Navigation zu /settings rendert der @team-Slot die /settings-Seite, während die aktuell aktive Seite für den @analytics-Slot beibehalten wird.

Beim Neuladen rendert Next.js eine default.js für @analytics. Wenn default.js nicht vorhanden ist, wird stattdessen ein 404-Fehler gerendert.

Da children ein impliziter Slot ist, müssen Sie auch eine default.js-Datei erstellen, um ein Fallback für children zu rendern, wenn Next.js den aktiven Zustand der übergeordneten Seite nicht wiederherstellen kann.

Verhalten

Standardmäßig behält Next.js den aktiven Zustand (oder die Unterseite) für jeden Slot bei. Der Inhalt, der innerhalb eines Slots gerendert wird, hängt jedoch von der Art der Navigation ab:

  • Soft Navigation: Bei der clientseitigen Navigation führt Next.js ein partielles Rendering durch, ändert die Unterseite innerhalb des Slots und behält die aktiven Unterseiten der anderen Slots bei, auch wenn sie nicht mit der aktuellen URL übereinstimmen.
  • Hard Navigation: Nach einem vollständigen Seitenladen (Browser-Neuladen) kann Next.js den aktiven Zustand für die Slots, die nicht mit der aktuellen URL übereinstimmen, nicht bestimmen. Stattdessen wird eine default.js-Datei für die nicht übereinstimmenden Slots gerendert oder 404, wenn default.js nicht vorhanden ist.

Gut zu wissen:

  • Der 404-Fehler für nicht übereinstimmende Routen stellt sicher, dass Sie nicht versehentlich eine parallele Route auf einer Seite rendern, für die sie nicht vorgesehen war.

Beispiele

Mit useSelectedLayoutSegment(s)

Sowohl useSelectedLayoutSegment als auch useSelectedLayoutSegments akzeptieren einen parallelRoutesKey-Parameter, mit dem Sie das aktive Routensegment innerhalb eines Slots auslesen können.

'use client'

import { useSelectedLayoutSegment } from 'next/navigation'

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

import { useSelectedLayoutSegment } from 'next/navigation'

export default function Layout({ auth }) {
  const loginSegment = useSelectedLayoutSegment('auth')
  // ...
}

Wenn ein Benutzer zu app/@auth/login (oder /login in der URL-Leiste) navigiert, ist loginSegment gleich dem String "login".

Bedingte Routen

Sie können parallele Routen verwenden, um Routen basierend auf bestimmten Bedingungen, wie der Benutzerrolle, bedingt zu rendern. Beispielsweise, um eine unterschiedliche Dashboard-Seite für die Rollen /admin oder /user zu rendern:

Diagramm der bedingten Routen
import { checkUserRole } from '@/lib/auth'

export default function Layout({
  user,
  admin,
}: {
  user: React.ReactNode
  admin: React.ReactNode
}) {
  const role = checkUserRole()
  return role === 'admin' ? admin : user
}
import { checkUserRole } from '@/lib/auth'

export default function Layout({ user, admin }) {
  const role = checkUserRole()
  return role === 'admin' ? admin : user
}

Tab-Gruppen

Sie können ein layout innerhalb eines Slots hinzufügen, um Benutzern zu ermöglichen, den Slot unabhängig zu navigieren. Dies ist nützlich für die Erstellung von Tabs.

Beispielsweise hat der @analytics-Slot zwei Unterseiten: /page-views und /visitors.

Analytics-Slot mit zwei Unterseiten und einem Layout

Erstellen Sie innerhalb von @analytics eine layout-Datei, um die Tabs zwischen den beiden Seiten zu teilen:

import Link from 'next/link'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <nav>
        <Link href="/page-views">Page Views</Link>
        <Link href="/visitors">Visitors</Link>
      </nav>
      <div>{children}</div>
    </>
  )
}
import Link from 'next/link'

export default function Layout({ children }) {
  return (
    <>
      <nav>
        <Link href="/page-views">Page Views</Link>
        <Link href="/visitors">Visitors</Link>
      </nav>
      <div>{children}</div>
    </>
  )
}

Modale Fenster

Parallele Routen können zusammen mit Intercepting Routes verwendet werden, um modale Fenster mit Deep Linking zu erstellen. Dies ermöglicht es Ihnen, häufige Herausforderungen beim Erstellen von modalen Fenstern zu lösen, wie:

  • Teilbarkeit des modalen Inhalts über eine URL.
  • Beibehaltung des Kontexts beim Aktualisieren der Seite, anstatt das modale Fenster zu schließen.
  • Schließen des modalen Fensters bei Rückwärtsnavigation, anstatt zur vorherigen Route zu gehen.
  • Wiederöffnen des modalen Fensters bei Vorwärtsnavigation.

Betrachten Sie das folgende UI-Muster, bei dem ein Benutzer ein Login-Modalfenster aus einem Layout mit clientseitiger Navigation öffnen oder eine separate /login-Seite aufrufen kann:

Diagramm der parallelen Routen mit Auth-Modalfenster

Um dieses Muster zu implementieren, erstellen Sie zunächst eine /login-Route, die Ihre Haupt-Login-Seite rendert.

Diagramm der parallelen Routen mit Login-Seite
import { Login } from '@/app/ui/login'

export default function Page() {
  return <Login />
}
import { Login } from '@/app/ui/login'

export default function Page() {
  return <Login />
}

Fügen Sie dann im @auth-Slot eine default.js-Datei hinzu, die null zurückgibt. Dadurch wird sichergestellt, dass das modale Fenster nicht gerendert wird, wenn es nicht aktiv ist.

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

Interzeptieren Sie im @auth-Slot die /login-Route, indem Sie den /(.)login-Ordner aktualisieren. Importieren Sie die <Modal>-Komponente und ihre Kinder in die /(.)login/page.tsx-Datei:

import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'

export default function Page() {
  return (
    <Modal>
      <Login />
    </Modal>
  )
}
import { Modal } from '@/app/ui/modal'
import { Login } from '@/app/ui/login'

export default function Page() {
  return (
    <Modal>
      <Login />
    </Modal>
  )
}

Gut zu wissen:

  • Die verwendete Konvention zum Abfangen der Route, z.B. (.), hängt von Ihrer Dateisystemstruktur ab. Siehe Intercepting Routes convention.
  • Durch die Trennung der <Modal>-Funktionalität vom modalen Inhalt (<Login>) können Sie sicherstellen, dass jeglicher Inhalt innerhalb des modalen Fensters, z.B. Formulare, Server Components sind. Siehe Interleaving Client and Server Components für weitere Informationen.

Öffnen des modalen Fensters

Nun können Sie den Next.js-Router nutzen, um das modale Fenster zu öffnen und zu schließen. Dies stellt sicher, dass die URL korrekt aktualisiert wird, wenn das modale Fenster geöffnet ist, und bei Vorwärts- und Rückwärtsnavigation.

Um das modale Fenster zu öffnen, übergeben Sie den @auth-Slot als Prop an das übergeordnete Layout und rendern ihn zusammen mit dem children-Prop.

import Link from 'next/link'

export default function Layout({
  auth,
  children,
}: {
  auth: React.ReactNode
  children: React.ReactNode
}) {
  return (
    <>
      <nav>
        <Link href="/login">Open modal</Link>
      </nav>
      <div>{auth}</div>
      <div>{children}</div>
    </>
  )
}
import Link from 'next/link'

export default function Layout({ auth, children }) {
  return (
    <>
      <nav>
        <Link href="/login">Open modal</Link>
      </nav>
      <div>{auth}</div>
      <div>{children}</div>
    </>
  )
}

Wenn der Benutzer auf den <Link> klickt, öffnet sich das modale Fenster, anstatt zur /login-Seite zu navigieren. Bei einem Neuladen oder initialen Laden navigiert der Aufruf von /login jedoch zur Haupt-Login-Seite.

Schließen des modalen Fensters

Sie können das modale Fenster schließen, indem Sie router.back() aufrufen oder die Link-Komponente verwenden.

'use client'

import { useRouter } from 'next/navigation'

export function Modal({ children }: { children: React.ReactNode }) {
  const router = useRouter()

  return (
    <>
      <button
        onClick={() => {
          router.back()
        }}
      >
        Close modal
      </button>
      <div>{children}</div>
    </>
  )
}
'use client'

import { useRouter } from 'next/navigation'

export function Modal({ children }) {
  const router = useRouter()

  return (
    <>
      <button
        onClick={() => {
          router.back()
        }}
      >
        Close modal
      </button>
      <div>{children}</div>
    </>
  )
}

Wenn Sie die Link-Komponente verwenden, um von einer Seite wegzunavigieren, die den @auth-Slot nicht mehr rendern soll, müssen Sie sicherstellen, dass die parallele Route auf eine Komponente verweist, die null zurückgibt. Zum Beispiel erstellen wir beim Navigieren zurück zur Root-Seite eine @auth/page.tsx-Komponente:

import Link from 'next/link'

export function Modal({ children }: { children: React.ReactNode }) {
  return (
    <>
      <Link href="/">Close modal</Link>
      <div>{children}</div>
    </>
  )
}
import Link from 'next/link'

export function Modal({ children }) {
  return (
    <>
      <Link href="/">Close modal</Link>
      <div>{children}</div>
    </>
  )
}
export default function Page() {
  return null
}
export default function Page() {
  return null
}

Oder wenn Sie zu einer anderen Seite navigieren (z.B. /foo, /foo/bar, etc.), können Sie einen Catch-all-Slot verwenden:

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

Gut zu wissen:

  • Wir verwenden eine Catch-all-Route in unserem @auth-Slot, um das modale Fenster zu schließen, aufgrund des Verhaltens von parallelen Routen(#behavior). Da clientseitige Navigationen zu einer Route, die nicht mehr mit dem Slot übereinstimmt, sichtbar bleiben, müssen wir den Slot auf eine Route verweisen, die null zurückgibt, um das modale Fenster zu schließen.
  • Weitere Beispiele könnten das Öffnen eines Foto-Modalfensters in einer Galerie sein, während auch eine dedizierte /photo/[id]-Seite vorhanden ist, oder das Öffnen eines Warenkorbs in einem seitlichen modalen Fenster.
  • Sehen Sie sich ein Beispiel für modale Fenster mit abgefangenen und parallelen Routen an.

Lade- und Fehler-UI

Parallele Routen können unabhängig voneinander gestreamt werden, sodass Sie unabhängige Fehler- und Ladezustände für jede Route definieren können:

Parallele Routen ermöglichen benutzerdefinierte Fehler- und Ladezustände

Weitere Informationen finden Sie in der Dokumentation zu Loading UI und Error Handling.