BackRetour au blog

RFC sur les Layouts

Routes imbriquées et layouts, routage côté client et serveur, fonctionnalités de React 18, et conçu pour les Server Components.

Ce RFC (Request for Comment) présente la plus grande mise à jour de Next.js depuis son introduction en 2016 :

  • Layouts imbriqués : Construisez des applications complexes avec des routes imbriquées.
  • Conçu pour les Server Components : Optimisé pour la navigation dans les sous-arbres.
  • Amélioration du chargement des données : Chargez dans les layouts tout en évitant les cascades.
  • Utilisation des fonctionnalités de React 18 : Streaming, Transitions et Suspense.
  • Routage côté client et serveur : Routage centré serveur avec un comportement SPA-like.
  • Adoption 100% progressive : Aucun changement cassant pour une adoption graduelle.
  • Modèles de routage avancés : Routes parallèles, routes d'interception, et plus encore.

Le nouveau routeur Next.js sera construit sur les fonctionnalités récemment publiées de React 18. Nous prévoyons d'introduire des conventions qui vous permettront d'adopter facilement ces nouvelles fonctionnalités et de profiter de leurs avantages.

Le travail sur ce RFC est en cours et nous annoncerons quand les nouvelles fonctionnalités seront disponibles. Pour donner votre avis, rejoignez la conversation sur Github Discussions.

Table des matières

Motivation

Nous avons recueilli les retours de la communauté via GitHub, Discord, Reddit et notre enquête développeur sur les limitations actuelles du routage dans Next.js. Nous avons constaté que :

  • L'expérience développeur pour créer des layouts peut être améliorée. Il devrait être facile de créer des layouts imbriqués, partagés entre routes et préservant leur état lors de la navigation.
  • De nombreuses applications Next.js sont des tableaux de bord ou consoles qui bénéficieraient de solutions de routage plus avancées.

Bien que le système de routage actuel ait bien fonctionné depuis les débuts de Next.js, nous voulons faciliter la construction d'applications web plus performantes et riches en fonctionnalités.

En tant que mainteneurs du framework, nous voulons aussi construire un système de routage rétrocompatible et aligné avec le futur de React.

Note : Certaines conventions de routage s'inspirent du routeur basé sur Relay chez Meta, où certaines fonctionnalités des Server Components ont été développées, ainsi que des routeurs côté client comme React Router et Ember.js. La convention de fichier layout.js s'inspire du travail effectué dans SvelteKit. Nous remercions également Cassidy pour avoir ouvert un RFC précédent sur les layouts.

Terminologie

Ce RFC introduit de nouvelles conventions et syntaxes de routage. La terminologie est basée sur React et les termes standards du web. Tout au long du RFC, vous verrez ces termes liés à leurs définitions ci-dessous.

  • Arbre : Convention pour visualiser une structure hiérarchique. Par exemple, un arbre de composants avec des composants parents et enfants, une structure de dossiers, etc.
  • Sous-arbre : Partie de l'arbre, commençant à la racine (premier) et se terminant aux feuilles (dernier).

  • Chemin URL : Partie de l'URL qui suit le domaine.
  • Segment URL : Partie du chemin URL délimitée par des slashs.

Fonctionnement actuel du routage

Aujourd'hui, Next.js utilise le système de fichiers pour mapper les dossiers et fichiers individuels dans le répertoire Pages aux routes accessibles via les URLs. Chaque fichier page exporte un composant React et a une route associée basée sur son nom de fichier. Par exemple :

Introduction du répertoire app

Pour garantir que ces nouvelles améliorations puissent être adoptées progressivement et éviter les changements cassants, nous proposons un nouveau répertoire appelé app.

Le répertoire app fonctionnera conjointement avec le répertoire pages. Vous pouvez déplacer progressivement des parties de votre application vers le nouveau répertoire app pour profiter des nouvelles fonctionnalités. Pour la rétrocompatibilité, le comportement du répertoire pages restera le même et continuera d'être supporté.

Définition des routes

Vous pouvez utiliser la hiérarchie des dossiers dans app pour définir les routes. Une route est un chemin unique de dossiers imbriqués, suivant la hiérarchie depuis le dossier racine jusqu'à un dossier feuille final.

Par exemple, vous pouvez ajouter une nouvelle route /dashboard/settings en imbriquant deux nouveaux dossiers dans le répertoire app.

Note :

  • Avec ce système, vous utiliserez les dossiers pour définir les routes et les fichiers pour définir l'UI (avec de nouvelles conventions de fichiers comme layout.js, page.js, et dans la deuxième partie du RFC loading.js).
  • Cela vous permet de colocaliser vos propres fichiers de projet (composants UI, fichiers de test, stories, etc.) dans le répertoire app. Actuellement, ce n'est possible qu'avec la configuration pageExtensions.

Segments de route

Chaque dossier dans le sous-arbre représente un segment de route. Chaque segment de route est mappé à un segment correspondant dans un chemin URL.

Par exemple, la route /dashboard/settings est composée de 3 segments :

  • Le segment racine /
  • Le segment dashboard
  • Le segment settings

Note : Le nom segment de route a été choisi pour correspondre à la terminologie existante autour des chemins URL.

Layouts

Nouvelle convention de fichier : layout.js

Jusqu'à présent, nous avons utilisé des dossiers pour définir les routes de notre application. Mais les dossiers vides ne font rien par eux-mêmes. Voyons comment vous pouvez définir l'UI qui sera rendue pour ces routes en utilisant de nouvelles conventions de fichiers.

Un layout est une UI partagée entre les segments de route dans un sous-arbre. Les layouts n'affectent pas les chemins URL et ne se re-rendent pas (l'état React est préservé) lorsqu'un utilisateur navigue entre des segments frères.

Un layout peut être défini en exportant par défaut un composant React depuis un fichier layout.js. Le composant doit accepter une prop children qui sera remplie avec les segments que le layout englobe.

Il existe 2 types de layouts :

  • Layout racine : S'applique à toutes les routes
  • Layout régulier : S'applique à des routes spécifiques

Vous pouvez imbriquer deux layouts ou plus pour former des layouts imbriqués.

Layout racine

Vous pouvez créer un layout racine qui s'appliquera à toutes les routes de votre application en ajoutant un fichier layout.js dans le dossier app.

Note :

  • Le layout racine remplace le besoin d'une App personnalisée (_app.js) et d'un Document personnalisé (_document.js) puisqu'il s'applique à toutes les routes.
  • Vous pourrez utiliser le layout racine pour personnaliser le shell initial du document (par ex. les balises <html> et <body>).
  • Vous pourrez charger des données dans le layout racine (et d'autres layouts).

Layouts réguliers

Vous pouvez aussi créer un layout qui ne s'applique qu'à une partie de votre application en ajoutant un fichier layout.js dans un dossier spécifique.

Par exemple, vous pouvez créer un fichier layout.js dans le dossier dashboard qui ne s'appliquera qu'aux segments de route à l'intérieur de dashboard.

Imbrication des layouts

Les layouts sont imbriqués par défaut.

Par exemple, si nous combinons les deux layouts ci-dessus. Le layout racine (app/layout.js) serait appliqué au layout dashboard, qui s'appliquerait aussi à tous les segments de route à l'intérieur de dashboard/*.

Pages

Nouvelle convention de fichier : page.js

Une page est une UI unique à un segment de route. Vous pouvez créer une page en ajoutant un fichier page.js dans un dossier.

Par exemple, pour créer des pages pour les routes /dashboard/*, vous pouvez ajouter un fichier page.js dans chaque dossier. Quand un utilisateur visite /dashboard/settings, Next.js rendra le fichier page.js du dossier settings encapsulé dans tous les layouts existants plus haut dans le sous-arbre.

Vous pouvez créer un fichier page.js directement dans le dossier dashboard pour correspondre à la route /dashboard. Le layout dashboard s'appliquera aussi à cette page :

Cette route est composée de 2 segments :

  • Le segment racine /
  • Le segment dashboard

Note :

  • Pour qu'une route soit valide, elle doit avoir une page dans son segment feuille. Sinon, la route générera une erreur.

Comportement des Layouts et Pages

  • Les extensions de fichiers js|jsx|ts|tsx peuvent être utilisées pour les Pages et Layouts.
  • Les composants Page sont l'export par défaut de page.js.
  • Les composants Layout sont l'export par défaut de layout.js.
  • Les composants Layout doivent accepter une prop children.

Quand un composant layout est rendu, la prop children sera remplie avec un layout enfant (s'il existe plus bas dans le sous-arbre) ou une page.

Il peut être plus facile de le visualiser comme un arbre de layouts où le layout parent choisira le layout enfant le plus proche jusqu'à atteindre une page.

Exemple :

app/layout.js
// Layout racine
// - S'applique à toutes les routes
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Header />
        {children}
        <Footer />
      </body>
    </html>
  );
}
app/dashboard/layout.js
// Layout régulier
// - S'applique aux segments de route dans app/dashboard/*
export default function DashboardLayout({ children }) {
  return (
    <>
      <DashboardSidebar />
      {children}
    </>
  );
}
app/dashboard/analytics/page.js
// Composant Page
// - L'UI pour le segment `app/dashboard/analytics`
// - Correspond au chemin URL `acme.com/dashboard/analytics`
export default function AnalyticsPage() {
  return <main>...</main>;
}

La combinaison ci-dessus de layouts et pages rendrait la hiérarchie de composants suivante :

Hiérarchie de composants
<RootLayout>
  <Header />
  <DashboardLayout>
    <DashboardSidebar />
    <AnalyticsPage>
      <main>...</main>
    </AnalyticsPage>
  </DashboardLayout>
  <Footer />
</RootLayout>

React Server Components

Note : React a introduit de nouveaux types de composants : Serveur, Client (composants React traditionnels), et Partagés. Pour en savoir plus sur ces nouveaux types, nous recommandons de lire le RFC des Server Components de React.

Avec ce RFC, vous pouvez commencer à utiliser les fonctionnalités de React et adopter progressivement les React Server Components dans votre application Next.js.

Les internes du nouveau système de routage exploiteront aussi les fonctionnalités récemment publiées de React comme le Streaming, les Transitions et le Suspense. Ce sont les briques de base des React Server Components.

Server Components par défaut

Un des plus grands changements entre les répertoires pages et app est que, par défaut, les fichiers dans app seront rendus côté serveur en tant que React Server Components.

Cela vous permettra d'adopter automatiquement les React Server Components lors de la migration de pages vers app.

Note : Les Server Components peuvent être utilisés dans le dossier app ou vos propres dossiers, mais ne peuvent pas être utilisés dans le répertoire pages pour des raisons de rétrocompatibilité.

Convention des composants Client et Serveur

Le dossier app prendra en charge les composants serveur, client et partagés, et vous pourrez imbriquer ces composants dans une arborescence.

Il y a une discussion en cours sur la convention exacte pour définir les composants Client et Serveur. Nous suivrons la résolution de cette discussion.

  • Pour l'instant, les composants Serveur peuvent être définis en ajoutant .server.js au nom du fichier. Par exemple : layout.server.js
  • Les composants Client peuvent être définis en ajoutant .client.js au nom du fichier. Par exemple : page.client.js.
  • Les fichiers .js sont considérés comme des composants partagés. Puisqu'ils peuvent être rendus côté Serveur et Client, ils doivent respecter les contraintes de chaque contexte.

Remarque :

  • Les composants Client et Serveur ont des contraintes qui doivent être respectées. Lorsque vous décidez d'utiliser un composant client ou serveur, nous recommandons d'utiliser les composants serveur (par défaut) jusqu'à ce que vous ayez besoin d'un composant client.

Hooks

Nous ajouterons des hooks pour les composants Client et Serveur qui vous permettront d'accéder à l'objet headers, aux cookies, aux noms de chemins, aux paramètres de recherche, etc. À l'avenir, nous aurons une documentation avec plus d'informations.

Environnements de rendu

Vous aurez un contrôle granulaire des composants qui seront inclus dans le bundle JavaScript côté client en utilisant la convention des composants Client et Serveur.

Par défaut, les routes dans app utiliseront la génération statique et passeront à un rendu dynamique lorsqu'un segment de route utilise des hooks côté serveur nécessitant un contexte de requête.

Imbrication des composants Client et Serveur dans une route

Dans React, il existe une restriction concernant l'importation de composants Serveur dans des composants Client, car les composants Serveur peuvent contenir du code réservé au serveur (par exemple, des utilitaires de base de données ou de système de fichiers).

Par exemple, importer le composant Serveur ne fonctionnerait pas :

ClientComponent.js
import ServerComponent from './ServerComponent.js';
 
export default function ClientComponent() {
  return (
    <>
      <ServerComponent />
    </>
  );
}

Cependant, un composant Serveur peut être passé comme enfant d'un composant Client. Vous pouvez le faire en l'encapsulant dans un autre composant Serveur. Par exemple :

ClientComponent.js
export default function ClientComponent({ children }) {
  return (
    <>
      <h1>Composant Client</h1>
      {children}
    </>
  );
}
 
// ServerComponent.js
export default function ServerComponent() {
  return (
    <>
      <h1>Composant Serveur</h1>
    </>
  );
}
 
// page.js
// Il est possible d'importer des composants Client et Serveur dans des composants Serveur
// car ce composant est rendu côté serveur
import ClientComponent from "./ClientComponent.js";
import ServerComponent from "./ServerComponent.js";
 
export default function ServerComponentPage() {
  return (
    <>
      <ClientComponent>
        <ServerComponent />
      </ClientComponent>
    </>
  );
}

Avec ce modèle, React saura qu'il doit rendre ServerComponent côté serveur avant d'envoyer le résultat (qui ne contient aucun code réservé au serveur) au client. Du point de vue du composant Client, son enfant sera déjà rendu.

Dans les layouts, ce modèle est appliqué avec la prop children pour éviter de créer un composant wrapper supplémentaire.

Par exemple, le composant ClientLayout acceptera le composant ServerPage comme enfant :

app/dashboard/layout.js
// Le Dashboard Layout est un composant Client
export default function ClientLayout({ children }) {
  // Peut utiliser useState / useEffect ici
  return (
    <>
      <h1>Layout</h1>
      {children}
    </>
  );
}
 
// La Page est un composant Serveur qui sera passé au Dashboard Layout
// app/dashboard/settings/page.js
export default function ServerPage() {
  return (
    <>
      <h1>Page</h1>
    </>
  );
}

Remarque : Ce style de composition est un modèle important pour le rendu de composants Serveur dans des composants Client. Il établit une norme à apprendre et est l'une des raisons pour lesquelles nous avons décidé d'utiliser la prop children.

Récupération de données

Il sera possible de récupérer des données à l'intérieur de plusieurs segments d'une route. Ceci diffère du dossier pages, où la récupération de données était limitée au niveau de la page.

Récupération de données dans les layouts

Vous pouvez récupérer des données dans un fichier layout.js en utilisant les méthodes de récupération de données de Next.js getStaticProps ou getServerSideProps.

Par exemple, un layout de blog pourrait utiliser getStaticProps pour récupérer des catégories depuis un CMS, qui pourraient être utilisées pour remplir un composant de sidebar :

app/blog/layout.js
export async function getStaticProps() {
  const categories = await getCategoriesFromCMS();
 
  return {
    props: { categories },
  };
}
 
export default function BlogLayout({ categories, children }) {
  return (
    <>
      <BlogSidebar categories={categories} />
      {children}
    </>
  );
}

Plusieurs méthodes de récupération de données dans une route

Vous pouvez également récupérer des données dans plusieurs segments d'une route. Par exemple, un layout qui récupère des données peut aussi encapsuler une page qui récupère ses propres données.

En reprenant l'exemple du blog, une page de post unique peut utiliser getStaticProps et getStaticPaths pour récupérer les données du post depuis un CMS :

app/blog/[slug]/page.js
export async function getStaticPaths() {
  const posts = await getPostSlugsFromCMS();
 
  return {
    paths: posts.map((post) => ({
      params: { slug: post.slug },
    })),
  };
}
 
export async function getStaticProps({ params }) {
  const post = await getPostFromCMS(params.slug);
 
  return {
    props: { post },
  };
}
 
export default function BlogPostPage({ post }) {
  return <Post post={post} />;
}

Puisque app/blog/layout.js et app/blog/[slug]/page.js utilisent tous deux getStaticProps, Next.js générera statiquement toute la route /blog/[slug] en tant que composants Serveur React au moment du build - ce qui réduira le JavaScript côté client et accélèrera l'hydratation.

Les routes générées statiquement améliorent encore cela, car la navigation côté client réutilise le cache (données des composants Serveur) et ne recalcule pas le travail, ce qui réduit le temps CPU car vous rendez un instantané des composants Serveur.

Comportement et priorité

Les méthodes de récupération de données de Next.js (getServerSideProps et getStaticProps) ne peuvent être utilisées que dans des composants Serveur du dossier app. Différentes méthodes de récupération de données dans les segments d'une même route s'influencent mutuellement.

L'utilisation de getServerSideProps dans un segment affectera getStaticProps dans d'autres segments. Comme une requête doit déjà aller vers un serveur pour le segment getServerSideProps, le serveur rendra également tous les segments getStaticProps. Il réutilisera les props récupérées au moment du build, donc les données resteront statiques, mais le rendu se fera à la demande à chaque requête avec les props générées pendant next build.

L'utilisation de getStaticProps avec revalidate (ISR) dans un segment affectera getStaticProps avec revalidate dans d'autres segments. S'il y a deux périodes de revalidation dans une route, la plus courte prévaudra.

Remarque : À l'avenir, cela pourrait être optimisé pour permettre une granularité complète de la récupération de données dans une route.

Récupération de données avec les composants Serveur React

La combinaison du routage côté serveur, des composants Serveur React, de Suspense et du streaming a quelques implications pour la récupération de données et le rendu dans Next.js :

Récupération de données parallèle

Next.js lancera les récupérations de données en parallèle pour minimiser les cascades. Par exemple, si la récupération de données était séquentielle, chaque segment imbriqué dans la route ne pourrait commencer à récupérer des données que lorsque le segment précédent aurait terminé. Avec la récupération parallèle, cependant, chaque segment peut commencer à récupérer des données simultanément.

Comme le rendu peut dépendre du contexte, le rendu de chaque segment commencera une fois que ses données auront été récupérées et que son parent aura terminé le rendu.

À l'avenir, avec Suspense, le rendu pourrait également commencer immédiatement - même si les données ne sont pas complètement chargées. Si les données sont lues avant d'être disponibles, Suspense sera déclenché. React commencera à rendre les composants Serveur de manière optimiste, avant que les requêtes ne soient terminées, et insérera le résultat au fur et à mesure que les requêtes se résolvent.

Récupération et rendu partiels

Lors de la navigation entre segments de route frères, Next.js ne récupérera et ne rendra qu'à partir de ce segment vers le bas. Il n'aura pas besoin de re-récupérer ou de re-rendre quoi que ce soit au-dessus. Cela signifie que dans une page partageant un layout, le layout sera préservé lorsqu'un utilisateur naviguera entre des pages frères, et Next.js ne récupérera et ne rendra qu'à partir de ce segment vers le bas.

Ceci est particulièrement utile pour les composants Serveur React, car sinon chaque navigation entraînerait un re-rendu complet de la page côté serveur au lieu de ne rendre que la partie modifiée de la page côté serveur. Cela réduit la quantité de données transférées et le temps d'exécution, améliorant ainsi les performances.

Par exemple, si l'utilisateur navigue entre les pages /analytics et /settings, React re-rendra les segments de page mais conservera les layouts :

Remarque : Il sera possible de forcer une re-récupération des données plus haut dans l'arborescence. Nous discutons encore des détails de cette fonctionnalité et mettrons à jour le RFC.

Groupes de routes

La hiérarchie du dossier app correspond directement aux chemins URL. Mais il est possible de sortir de ce modèle en créant un groupe de routes. Les groupes de routes peuvent être utilisés pour :

  • Organiser les routes sans affecter la structure URL.
  • Exclure un segment de route d'un layout.
  • Créer plusieurs layouts racine en divisant l'application.

Convention

Un groupe de routes peut être créé en entourant le nom d'un dossier de parenthèses : (nomDossier)

Remarque : Le nommage des groupes de routes est uniquement à des fins d'organisation, car il n'affecte pas le chemin URL.

Exemple : Exclure une route d'un layout

Pour exclure une route d'un layout, créez un nouveau groupe de routes (par exemple (shop)) et déplacez les routes partageant le même layout dans le groupe (par exemple account et cart). Les routes en dehors du groupe ne partageront pas le layout (par exemple checkout).

Avant :

Après :

Exemple : Organiser les routes sans affecter le chemin URL

De même, pour organiser les routes, créez un groupe pour garder les routes associées ensemble. Les dossiers entre parenthèses seront omis de l'URL (par exemple (marketing) ou (shop)).

Exemple : Créer plusieurs layouts racine

Pour créer plusieurs layouts racine, créez deux groupes de routes ou plus au niveau supérieur du dossier app. Ceci est utile pour partitionner une application en sections ayant une interface ou une expérience complètement différente. Les balises <html>, <body> et <head> de chaque layout racine peuvent être personnalisées séparément.

Routage centré serveur

Actuellement, Next.js utilise un routage côté client. Après le chargement initial et lors des navigations ultérieures, une requête est envoyée au serveur pour les ressources de la nouvelle page. Cela inclut le JavaScript pour chaque composant (y compris les composants affichés uniquement sous certaines conditions) et leurs props (données JSON provenant de getServerSideProps ou getStaticProps). Une fois que le JavaScript et les données sont chargés depuis le serveur, React rend les composants côté client.

Dans ce nouveau modèle, Next.js utilisera un routage centré serveur tout en conservant les transitions côté client. Cela s'aligne avec les composants Serveur qui sont évalués côté serveur.

Lors de la navigation, les données sont récupérées et React rend les composants côté serveur. La sortie du serveur est constituée d'instructions spéciales (pas du HTML ou du JSON) pour React côté client afin de mettre à jour le DOM. Ces instructions contiennent le résultat du rendu des composants Serveur, ce qui signifie qu'aucun JavaScript pour ce composant n'a besoin d'être chargé dans le navigateur pour afficher le résultat.

Ceci contraste avec le comportement par défaut actuel des composants Client, où le JavaScript du composant est envoyé au navigateur pour être rendu côté client.

Certains avantages du routage centré serveur avec les composants Serveur React incluent :

  • Le routage utilise la même requête que celle utilisée pour les composants Serveur (aucune requête serveur supplémentaire n'est effectuée).
  • Moins de travail est effectué côté serveur car la navigation entre routes ne récupère et ne rend que les segments qui changent.
  • Aucun JavaScript supplémentaire n'est chargé dans le navigateur lors d'une navigation côté client lorsqu'aucun nouveau composant client n'est utilisé.
  • Le routeur utilise un nouveau protocole de streaming pour que le rendu puisse commencer avant que toutes les données ne soient chargées.

Lorsque les utilisateurs naviguent dans une application, le routeur stockera le résultat de la charge utile des composants Serveur React dans un cache côté client en mémoire. Le cache est divisé par segments de route, ce qui permet une invalidation à n'importe quel niveau et garantit la cohérence entre les rendus concurrents. Cela signifie que dans certains cas, le cache d'un segment précédemment récupéré peut être réutilisé.

Remarque

  • La génération statique et la mise en cache côté serveur peuvent être utilisées pour optimiser la récupération de données.
  • Les informations ci-dessus décrivent le comportement des navigations ultérieures. Le chargement initial est un processus différent impliquant le rendu côté serveur pour générer du HTML.
  • Bien que le routage côté client ait bien fonctionné pour Next.js, il ne s'adapte pas bien lorsque le nombre de routes potentielles est important car le client doit télécharger une carte des routes.
  • Globalement, en utilisant les composants Serveur React, la navigation côté client est plus rapide car nous chargeons et rendons moins de composants dans le navigateur.

États de chargement instantanés

Avec le routage côté serveur, la navigation se produit après la récupération des données et le rendu, il est donc important d'afficher une interface de chargement pendant que les données sont récupérées, sinon l'application semblera ne pas répondre.

Le nouveau routeur utilisera Suspense pour les états de chargement instantanés et des squelettes par défaut. Cela signifie que l'interface de chargement peut être affichée immédiatement pendant que le contenu du nouveau segment se charge. Le nouveau contenu est ensuite échangé une fois que le rendu côté serveur est terminé.

Pendant le rendu :

  • La navigation vers la nouvelle route sera immédiate.
  • Les layouts partagés resteront interactifs pendant que les nouveaux segments de route se chargent.
  • La navigation sera interruptible - ce qui signifie que l'utilisateur peut naviguer entre les routes pendant que le contenu d'une route se charge.

Squelettes de chargement par défaut

Les limites de suspense (Suspense boundaries) seront gérées automatiquement en arrière-plan avec une nouvelle convention de fichier appelée loading.js.

Exemple :

Vous pourrez créer un squelette de chargement par défaut en ajoutant un fichier loading.js dans un dossier.

Le fichier loading.js doit exporter un composant React :

loading.js
export default function Loading() {
  return <YourSkeleton />
}
 
// layout.js
export default function Layout({children}) {
  return (
    <>
      <Sidebar />
      {children}
    </>
  )
}
 
// Output
<>
  <Sidebar />
  <Suspense fallback={<Loading />}>{children}</Suspense>
</>

Cela entraînera l'encapsulation de tous les segments du dossier dans une limite de suspense (Suspense boundary). Le squelette par défaut sera utilisé lors du premier chargement de la mise en page (layout) et lors de la navigation entre les pages sœurs.

Gestion des erreurs

Les limites d'erreur (Error boundaries) sont des composants React qui interceptent les erreurs JavaScript n'importe où dans leur arborescence de composants enfants.

Convention

Vous pourrez créer une limite d'erreur (Error Boundary) qui interceptera les erreurs dans une sous-arborescence en ajoutant un fichier error.js et en exportant par défaut un composant React.

Le composant sera affiché comme solution de repli (fallback) si une erreur est levée dans cette sous-arborescence. Ce composant peut être utilisé pour enregistrer les erreurs, afficher des informations utiles sur l'erreur et fournir des fonctionnalités pour tenter de récupérer après l'erreur.

En raison de la nature imbriquée des segments et des mises en page (layouts), la création de limites d'erreur (Error boundaries) vous permet d'isoler les erreurs à ces parties de l'interface utilisateur. Lors d'une erreur, les mises en page situées au-dessus de la limite resteront interactives et leur état sera préservé.

error.js
export default function Error({ error, reset }) {
  return (
    <>
      Une erreur est survenue : {error.message}
      <button onClick={() => reset()}>Réessayer</button>
    </>
  );
}
 
// layout.js
export default function Layout({children}) {
  return (
    <>
      <Sidebar />
      {children}
    </>
  )
}
 
// Output
<>
  <Sidebar />
  <ErrorBoundary fallback={<Error />}>{children}</ErrorBoundary>
</>

Remarque :

  • Les erreurs dans un fichier layout.js situé dans le même segment qu'un fichier error.js ne seront pas interceptées car la limite d'erreur automatique encapsule les enfants d'une mise en page (layout) et non la mise en page elle-même.

Modèles (Templates)

Les modèles (templates) sont similaires aux mises en page (layouts) en ce qu'ils encapsulent chaque mise en page enfant ou page.

Contrairement aux mises en page qui persistent entre les routes et conservent leur état, les modèles créent une nouvelle instance pour chacun de leurs enfants. Cela signifie que lorsqu'un utilisateur navigue entre des segments de route partageant un modèle, une nouvelle instance du composant est montée.

Remarque : Nous recommandons d'utiliser des mises en page (layouts) sauf si vous avez une raison spécifique d'utiliser un modèle.

Convention

Un modèle peut être défini en exportant un composant React par défaut depuis un fichier template.js. Le composant doit accepter une prop children qui sera remplie avec les segments imbriqués.

Exemple

template.js
export default function Template({ children }) {
  return <Container>{children}</Container>;
}

Le rendu d'un segment de route avec une mise en page (layout) et un modèle sera comme suit :

<Layout>
  {/* Notez que le modèle reçoit une clé unique. */}
  <Template key={routeParam}>{children}</Template>
</Layout>

Comportement

Il peut y avoir des cas où vous devez monter et démonter une interface utilisateur partagée, et les modèles seraient une option plus appropriée. Par exemple :

  • Animations d'entrée/sortie utilisant CSS ou des bibliothèques d'animation
  • Fonctionnalités reposant sur useEffect (par exemple, journalisation des vues de page) et useState (par exemple, un formulaire de feedback par page)
  • Pour modifier le comportement par défaut du framework. Par exemple, les limites de suspense (Suspense boundaries) dans les mises en page n'affichent la solution de repli (fallback) que la première fois que la mise en page est chargée et non lors du changement de page. Pour les modèles, la solution de repli est affichée à chaque navigation.

Par exemple, considérez la conception d'une mise en page imbriquée avec un conteneur bordé qui doit encapsuler chaque sous-page.

Vous pourriez placer le conteneur dans la mise en page parente (shop/layout.js) :

shop/layout.js
export default function Layout({ children }) {
  return <div className="container">{children}</div>;
}
 
// shop/page.js
export default function Page() {
  return <div>...</div>;
}
 
// shop/categories/layout.js
export default function CategoryLayout({ children }) {
  return <div>{children}</div>;
}

Mais aucune animation d'entrée/sortie ne serait jouée lors du changement de page car la mise en page parente partagée ne se recharge pas.

Vous pourriez placer le conteneur dans chaque mise en page imbriquée ou page :

shop/layout.js
export default function Layout({ children }) {
  return <div>{children}</div>;
}
 
// shop/page.js
export default function Page() {
  return <div className="container">...</div>;
}
 
// shop/categories/layout.js
export default function CategoryLayout({ children }) {
  return <div className="container">{children}</div>;
}

Mais vous devriez alors le placer manuellement dans chaque mise en page imbriquée ou page, ce qui peut être fastidieux et source d'erreurs dans des applications plus complexes.

Avec cette convention, vous pouvez partager des modèles entre les routes qui créent une nouvelle instance lors de la navigation. Cela signifie que les éléments DOM seront recréés, l'état ne sera pas préservé et les effets seront resynchronisés.

Modèles de routage avancés

Nous prévoyons d'introduire des conventions pour couvrir les cas particuliers et vous permettre d'implémenter des modèles de routage plus avancés. Voici quelques exemples auxquels nous avons activement réfléchi :

Routes d'interception

Parfois, il peut être utile d'intercepter des segments de route depuis d'autres routes. Lors de la navigation, l'URL sera mise à jour normalement, mais le segment intercepté sera affiché dans la mise en page de la route actuelle.

Exemple

Avant : Cliquer sur l'image mène à une nouvelle route avec sa propre mise en page.

Après : En interceptant la route, cliquer sur l'image charge maintenant le segment dans la mise en page de la route actuelle. Par exemple, comme une modale.

Pour intercepter la route /photo/[id] depuis le segment /[username], créez un dossier dupliqué /photo/[id] à l'intérieur du dossier /[username] et préfixez-le avec la convention (..).

Convention

  • (..) - correspondra au segment de route un niveau supérieur (frère du répertoire parent). Similaire à ../ dans les chemins relatifs.
  • (..)(..) - correspondra au segment de route deux niveaux supérieurs. Similaire à ../../ dans les chemins relatifs.
  • (...) - correspondra au segment de route dans le répertoire racine.

Remarque : Actualiser ou partager la page chargera la route avec sa mise en page par défaut.

Routes parallèles dynamiques

Parfois, il peut être utile d'afficher deux segments feuilles (page.js) ou plus dans la même vue qui peuvent être navigués indépendamment.

Prenez par exemple deux groupes d'onglets ou plus dans le même tableau de bord. La navigation dans un groupe d'onglets ne devrait pas affecter l'autre. Les combinaisons d'onglets devraient également être correctement restaurées lors de la navigation avant et arrière.

Convention

Par défaut, les mises en page acceptent une prop appelée children qui contiendra une mise en page imbriquée ou une page. Vous pouvez renommer cette prop en créant un "emplacement" nommé (un dossier incluant le préfixe @) et en imbriquant des segments à l'intérieur.

Après cette modification, la mise en page recevra une prop appelée customProp au lieu de children.

analytics/layout.js
export default function Layout({ customProp }) {
  return <>{customProp}</>;
}

Vous pouvez créer des routes parallèles en ajoutant plus d'un emplacement nommé au même niveau. Dans l'exemple ci-dessous, @views et @audience sont tous deux passés comme props à la mise en page analytics.

Vous pouvez utiliser les emplacements nommés pour afficher simultanément des segments feuilles.

analytics/layout.js
export default function Layout({ views, audience }) {
  return (
    <>
      <div>
        <ViewsNav />
        {views}
      </div>
      <div>
        <AudienceNav />
        {audience}
      </div>
    </>
  );
}

Lorsque l'utilisateur navigue pour la première fois vers /analytics, le segment page.js dans chaque dossier (@views et @audience) est affiché.

Lors de la navigation vers /analytics/subscribers, seul @audience est mis à jour. De même, seul @views est mis à jour lors de la navigation vers /analytics/impressions.

La navigation avant et arrière rétablira la combinaison correcte de routes parallèles.

Combinaison des routes d'interception et parallèles

Vous pouvez combiner les routes d'interception et parallèles pour obtenir des comportements de routage spécifiques dans votre application.

Exemple

Par exemple, lors de la création d'une modale, vous devez souvent être conscient de certains défis courants, tels que :

  • Les modales ne sont pas accessibles via une URL.
  • Les modales se ferment lors de l'actualisation de la page.
  • La navigation arrière revient à la route précédente plutôt qu'à la route derrière la modale.
  • La navigation avant ne rouvre pas la modale.

Vous pouvez vouloir que la modale mette à jour l'URL lorsqu'elle s'ouvre, et que la navigation avant/arrière ouvre et ferme la modale. De plus, lors du partage de l'URL, vous pouvez vouloir que la page se charge avec la modale ouverte et le contexte derrière elle, ou vous pouvez vouloir que la page se charge avec le contenu sans la modale.

Un bon exemple de cela sont les photos sur les réseaux sociaux. Généralement, les photos sont accessibles dans une modale depuis le fil d'actualité ou le profil de l'utilisateur. Mais lors du partage de la photo, elles sont affichées directement sur leur propre page.

En utilisant des conventions, nous pouvons faire correspondre le comportement des modales au comportement de routage par défaut.

Considérez cette structure de dossier :

Avec ce modèle :

  • Le contenu de /photo/[id] est accessible via une URL dans son propre contexte. Il est également accessible dans une modale depuis la route /[username].
  • La navigation avant et arrière en utilisant la navigation côté client devrait fermer et rouvrir la modale.
  • L'actualisation de la page (navigation côté serveur) devrait amener l'utilisateur à la route originale /photo/id au lieu d'afficher la modale.

Dans /@modal/(..)photo/[id]/page.js, vous pouvez retourner le contenu de la page encapsulé dans un composant modale.

/@modal/(..)photo/[id]/page.js
export default function PhotoPage() {
  const router = useRouter();
 
  return (
    <Modal
      // la modale doit toujours être affichée au chargement de la page
      isOpen={true}
      // fermer la modale doit ramener l'utilisateur à la page précédente
      onClose={() => router.back()}
    >
      {/* Contenu de la page */}
    </Modal>
  );
}

Remarque : Cette solution n'est pas la seule façon de créer une modale dans Next.js, mais vise à montrer comment vous pouvez combiner des conventions pour obtenir un comportement de routage plus complexe.

Routes conditionnelles

Parfois, vous pouvez avoir besoin d'informations dynamiques comme des données ou un contexte pour déterminer quelle route afficher. Vous pouvez utiliser des routes parallèles pour charger conditionnellement une route ou une autre.

Exemple

layout.js
export async function getServerSideProps({ params }) {
  const { accountType } = await fetchAccount(params.slug);
  return { props: { isUser: accountType === 'user' } };
}
 
export default function UserOrTeamLayout({ isUser, user, team }) {
  return <>{isUser ? user : team}</>;
}

Dans l'exemple ci-dessus, vous pouvez retourner soit la route user soit la route team en fonction du slug. Cela vous permet de charger conditionnellement les données et de faire correspondre les sous-routes à une option ou à l'autre.

Conclusion

Nous sommes enthousiastes quant à l'avenir des mises en page (layouts), du routage et de React 18 dans Next.js. Le travail d'implémentation a commencé et nous annoncerons les fonctionnalités une fois qu'elles seront disponibles.

Laissez des commentaires et rejoignez la conversation sur GitHub Discussions.