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 hautement 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

Emplacements (Slots)

Les routes parallèles sont créées à l'aide d'emplacements nommés. Les emplacements sont définis avec la convention @folder. 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 @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.

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.

État actif et navigation

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

  • Navigation douce : Lors de la 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 page (actualisation 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 :

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

default.js

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

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

Routes parallèles 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, un 404 est affiché à 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.

useSelectedLayoutSegment(s)

Les fonctions useSelectedLayoutSegment et useSelectedLayoutSegments acceptent 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".

Exemples

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 }: { children: React.ReactNode }) {
  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. 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 actualisé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 utiliser 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 utilisons une route catch-all qui retourne null.

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 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 décrit dans État actif et navigation. 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 diffusé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.