Routes parallèles

Les routes parallèles vous permettent d'afficher simultanément ou conditionnellement une ou plusieurs pages dans la même mise en page. Elles sont utiles pour les sections très dynamiques d'une application, comme les tableaux de bord et les flux sur les réseaux sociaux.

Par exemple, pour un tableau de bord, vous pouvez utiliser des routes parallèles pour afficher simultanément les pages team et analytics :

Diagramme des routes parallèles

Convention

Emplacements (Slots)

Les routes parallèles sont créées à l'aide d'emplacements nommés. Les emplacements sont définis avec la convention @dossier. Par exemple, la structure de fichiers suivante définit deux emplacements : @analytics et @team :

Structure de fichiers des routes parallèles

Les emplacements sont passés comme props au layout parent partagé. Pour l'exemple ci-dessus, le composant dans app/layout.js accepte maintenant les props d'emplacement @analytics et @team, et peut les afficher en parallèle avec la prop children :

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}
    </>
  )
}

Cependant, les emplacements ne sont pas des segments de route et n'affectent pas la structure de l'URL. Par exemple, pour /@analytics/views, l'URL sera /views car @analytics est un emplacement. Les emplacements sont combinés avec le composant Page standard pour former la page finale associée au segment de route. Pour cette raison, vous ne pouvez pas avoir des emplacements statiques et dynamiques séparés au même niveau de segment de route. Si un emplacement est dynamique, tous les emplacements à ce niveau doivent être dynamiques.

Bon à savoir :

  • La prop children est un emplacement implicite qui n'a pas besoin d'être mappé à un dossier. Cela signifie que app/page.js est équivalent à app/@children/page.js.

default.js

Vous pouvez définir un fichier default.js pour servir de solution de repli pour les emplacements non correspondants lors du chargement initial ou du rechargement complet de la page.

Considérez la structure de dossiers suivante. L'emplacement @team a une page /settings, mais @analytics n'en a pas.

Routes parallèles avec routes non correspondantes

Lors de la navigation vers /settings, l'emplacement @team affichera la page /settings tout en conservant la page active actuelle pour l'emplacement @analytics.

Lors d'un rechargement, Next.js affichera un default.js pour @analytics. Si default.js n'existe pas, une 404 est affichée à la place.

De plus, comme children est un emplacement implicite, vous devez également créer un fichier default.js pour afficher une solution de repli pour children lorsque Next.js ne peut pas récupérer l'état actif de la page parente.

Comportement

Par défaut, Next.js garde une trace de l'état actif (ou sous-page) pour chaque emplacement. Cependant, le contenu affiché dans un emplacement dépendra du type de navigation :

  • Navigation douce : Lors d'une navigation côté client, Next.js effectuera un rendu partiel, changeant la sous-page dans l'emplacement, tout en conservant les sous-pages actives des autres emplacements, même si elles ne correspondent pas à l'URL actuelle.
  • Navigation complète : Après un chargement complet de la page (rechargement du navigateur), Next.js ne peut pas déterminer l'état actif pour les emplacements qui ne correspondent pas à l'URL actuelle. À la place, il affichera un fichier default.js pour les emplacements non correspondants, ou 404 si default.js n'existe pas.

Bon à savoir :

  • La 404 pour les routes non correspondantes aide à s'assurer que vous n'affichez pas accidentellement une route parallèle sur une page pour laquelle elle n'était pas prévue.

Exemples

Avec useSelectedLayoutSegment(s)

useSelectedLayoutSegment et useSelectedLayoutSegments acceptent tous deux un paramètre parallelRoutesKey, qui vous permet de lire le segment de route actif dans un emplacement.

'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')
  // ...
}

Lorsqu'un utilisateur navigue vers app/@auth/login (ou /login dans la barre d'URL), loginSegment sera égal à la chaîne "login".

Routes conditionnelles

Vous pouvez utiliser les routes parallèles pour afficher conditionnellement des routes en fonction de certaines conditions, comme le rôle de l'utilisateur. Par exemple, pour afficher une page de tableau de bord différente pour les rôles /admin ou /user :

Diagramme des routes conditionnelles
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
}

Groupes d'onglets

Vous pouvez ajouter un layout dans un emplacement pour permettre aux utilisateurs de naviguer dans l'emplacement de manière indépendante. C'est utile pour créer des onglets.

Par exemple, l'emplacement @analytics a deux sous-pages : /page-views et /visitors.

Emplacement analytics avec deux sous-pages et un layout

Dans @analytics, créez un fichier layout pour partager les onglets entre les deux pages :

import Link from 'next/link'

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

export default function Layout({ children }) {
  return (
    <>
      <nav>
        <Link href="/page-views">Vues de page</Link>
        <Link href="/visitors">Visiteurs</Link>
      </nav>
      <div>{children}</div>
    </>
  )
}

Modales

Les routes parallèles peuvent être utilisées avec les routes d'interception pour créer des modales qui prennent en charge le deep linking. Cela vous permet de résoudre des défis courants lors de la création de modales, tels que :

  • Rendre le contenu de la modale partageable via une URL.
  • Préserver le contexte lorsque la page est rechargée, au lieu de fermer la modale.
  • Fermer la modale lors d'une navigation arrière plutôt que de revenir à la route précédente.
  • Rouvrir la modale lors d'une navigation avant.

Considérez le modèle d'interface utilisateur suivant, où un utilisateur peut ouvrir une modale de connexion à partir d'un layout en utilisant la navigation côté client, ou accéder à une page /login séparée :

Diagramme des routes parallèles

Pour implémenter ce modèle, commencez par créer une route /login qui affiche votre page de connexion principale.

Diagramme des routes parallèles
import { Login } from '@/app/ui/login'

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

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

Ensuite, dans l'emplacement @auth, ajoutez un fichier default.js qui retourne null. Cela garantit que la modale n'est pas affichée lorsqu'elle n'est pas active.

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

Dans votre emplacement @auth, interceptez la route /login en mettant à jour le dossier /(.)login. Importez le composant <Modal> et ses enfants dans le fichier /(.)login/page.tsx :

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>
  )
}

Bon à savoir :

Ouverture de la modale

Maintenant, vous pouvez exploiter le routeur Next.js pour ouvrir et fermer la modale. Cela garantit que l'URL est correctement mise à jour lorsque la modale est ouverte, et lors des navigations avant et arrière.

Pour ouvrir la modale, passez l'emplacement @auth comme prop au layout parent et affichez-le avec la prop children.

import Link from 'next/link'

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

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

Lorsque l'utilisateur clique sur le <Link>, la modale s'ouvrira au lieu de naviguer vers la page /login. Cependant, lors d'un rechargement ou d'un chargement initial, la navigation vers /login amènera l'utilisateur à la page de connexion principale.

Fermeture de la modale

Vous pouvez fermer la modale en appelant router.back() ou en utilisant le composant Link.

'use client'

import { useRouter } from 'next/navigation'

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

  return (
    <>
      <button
        onClick={() => {
          router.back()
        }}
      >
        Fermer la modale
      </button>
      <div>{children}</div>
    </>
  )
}
'use client'

import { useRouter } from 'next/navigation'

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

  return (
    <>
      <button
        onClick={() => {
          router.back()
        }}
      >
        Fermer la modale
      </button>
      <div>{children}</div>
    </>
  )
}

Lorsque vous utilisez le composant Link pour naviguer vers une page qui ne devrait plus afficher l'emplacement @auth, nous devons nous assurer que la route parallèle correspond à un composant qui retourne null. Par exemple, lors de la navigation vers la page racine, nous créons un composant @auth/page.tsx :

import Link from 'next/link'

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

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

Ou si vous naviguez vers une autre page (comme /foo, /foo/bar, etc.), vous pouvez utiliser un emplacement catch-all :

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

Bon à savoir :

  • Nous utilisons une route catch-all dans notre emplacement @auth pour fermer la modale en raison du comportement des routes parallèles(#behavior). Comme les navigations côté client vers une route qui ne correspond plus à l'emplacement resteront visibles, nous devons faire correspondre l'emplacement à une route qui retourne null pour fermer la modale.
  • D'autres exemples pourraient inclure l'ouverture d'une modale photo dans une galerie tout en ayant une page dédiée /photo/[id], ou l'ouverture d'un panier d'achat dans une modale latérale.
  • Voir un exemple de modales avec des routes interceptées et parallèles.

UI de chargement et d'erreur

Les routes parallèles peuvent être streamées indépendamment, vous permettant de définir des états d'erreur et de chargement indépendants pour chaque route :

Les routes parallèles permettent des états d'erreur et de chargement personnalisés

Voir la documentation UI de chargement et Gestion des erreurs pour plus d'informations.