Retour au cours

frontend / nextjs

Layouts imbriqués

Leçon 21 exercice

Explication

Ce que vous allez apprendre

  • Comprendre pourquoi layout.tsx existe et comment il évite de dupliquer un menu ou un footer
  • Empiler plusieurs layouts selon l'arborescence de dossiers de l'App Router
  • Distinguer layout.tsx (persistant, état préservé) de template.tsx (recréé à chaque navigation)
  • Organiser des routes avec des Route Groups (nom) sans modifier l'URL
  • Afficher plusieurs vues indépendantes sur une même URL avec les Parallel Routes (@slot)

Dans quel contexte ?

Une équipe travaille sur une application qui a deux visages bien distincts : un site marketing public (/, /pricing) avec un grand header et un footer commercial, et un espace /dashboard avec une barre latérale de navigation, sans aucun rapport visuel avec le premier. Sans layouts imbriqués, il faudrait soit dupliquer le code de chaque en-tête dans chaque page, soit tout mélanger dans un seul layout racine bourré de conditions. Cette leçon montre comment Next.js règle ça proprement, dossier par dossier.

Partons du problème concret

Sur la plupart des sites, une partie de l'interface ne change jamais. Un menu, une barre latérale, un pied de page restent identiques d'une page à l'autre, seul le contenu central bouge.

Sans outil dédié, tu aurais deux mauvaises solutions. Soit recopier ce menu dans chaque page (source d'erreurs), soit le re-rendre à chaque navigation, avec un clignotement visible à l'écran.

L'App Router règle ça avec les fichiers layout.tsx. Chaque dossier de route peut définir le sien, et ces layouts s'empilent automatiquement selon l'arborescence des dossiers.

Un exemple pour visualiser l'empilement. Le layout de /dashboard entoure toutes les pages sous /dashboard/*, un peu comme des poupées russes qui s'emboîtent les unes dans les autres.

Maintenant, le point le plus important de cette leçon : que se passe-t-il pendant la navigation ? Quand tu passes de /dashboard/settings à /dashboard/analytics, le layout.tsx de /dashboard ne se recharge PAS. Seul le contenu à l'intérieur (children) change.

Concrètement, ça veut dire quoi ? Un menu déroulant resté ouvert, une position de scroll ou un état local dans le layout restent intacts pendant la navigation, sans que tu aies rien à faire.

Il existe une alternative pour le cas inverse : template.tsx. Contrairement à layout.tsx, il est recréé à CHAQUE navigation, ce qui est utile pour déclencher une animation d'entrée à chaque changement de page.

FichierRecréé à chaque navigationÉtat préservéCas d'usage typique
layout.tsxNonOuiMenu, sidebar, footer partagés
template.tsxOuiNonAnimation d'entrée, reset de formulaire

Prérequis

Cette leçon suppose que tu es à l'aise avec l'App Router et la notion de Server Component vue dans la leçon précédente sur l'introduction à Next.js.

Une fois ces deux fichiers compris, deux outils avancés viennent compléter le système. Les Route Groups (nom) organisent des dossiers sans que ce nom apparaisse dans l'URL — pratique pour donner un layout différent à une section "marketing" et à une section "app" sans changer les adresses. Les Parallel Routes (@slot) affichent plusieurs "vues" indépendantes sur une même URL, comme une modale par-dessus une page.

Le piège le plus fréquent ici : confondre layout.tsx (persistant, état préservé) et template.tsx (recréé à chaque fois). Si une animation d'entrée ne se déclenche jamais en changeant de page, c'est très probablement qu'un layout.tsx a été utilisé à la place d'un template.tsx.

Piège fréquent

Mettre un useState d'ouverture/fermeture de menu dans le layout racine app/layout.tsx, puis s'étonner qu'il reste "coincé" ouvert en naviguant entre /dashboard/settings et /dashboard/analytics. C'est le comportement normal d'un layout persistant, pas un bug — la solution est soit d'accepter cet état partagé, soit de le redescendre dans un template.tsx si un reset est voulu.

Commandes & code

Layouts imbriqués

Chaque segment de route peut avoir son propre layout.tsx. Ils s'imbriquent automatiquement.

text
app/
├── layout.tsx              # Layout racine
├── dashboard/
│   ├── layout.tsx           # Layout partagé par toutes les sous-routes /dashboard/*
│   ├── page.tsx              # /dashboard
│   ├── settings/
│   │   └── page.tsx           # /dashboard/settings
│   └── analytics/
│       └── page.tsx           # /dashboard/analytics
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="dashboard-grid">
      <aside>
        <nav>
          <a href="/dashboard/settings">Paramètres</a>
          <a href="/dashboard/analytics">Analytics</a>
        </nav>
      </aside>
      {/* IMPORTANT : un layout ne se re-render PAS entre les navigations
          internes à /dashboard — seul {children} change */}
      <main>{children}</main>
    </div>
  );
}
tsx
// app/template.tsx — comme un layout, mais recréé à CHAQUE navigation
// utile pour des animations d'entrée ou reset d'état local
"use client";

import { motion } from "framer-motion";

export default function Template({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 8 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.2 }}
    >
      {children}
    </motion.div>
  );
}
tsx
// Route Groups : (marketing) n'apparaît PAS dans l'URL
// app/(marketing)/layout.tsx -> layout pour "/", "/pricing"
// app/(app)/layout.tsx       -> layout différent pour "/dashboard"
export default function MarketingLayout({ children }: { children: React.ReactNode }) {
  return (
    <>
      <header>Header marketing (logo, CTA)</header>
      {children}
      <footer>Footer marketing</footer>
    </>
  );
}
tsx
// app/dashboard/settings/@modal/page.tsx — Parallel Routes (slots nommés)
// permet d'afficher une modale en plus du contenu principal, sur la même URL
export default function SettingsModal() {
  return <div className="modal">Modale de paramètres</div>;
}
FichierRecréé à chaque navigationPréserve l'état
layout.tsxNonOui
template.tsxOuiNon

Résumé

  • Les layouts s'imbriquent selon l'arborescence de dossiers.
  • Un layout ne re-render pas lors de la navigation entre ses enfants (état préservé).
  • Les Route Groups (nom) organisent le code sans impacter l'URL.
  • Les Parallel Routes (@slot) permettent d'afficher plusieurs vues indépendantes sur une même page.

Exercices pratiques

1 disponible
1

Mission : le menu qui reste coincé ouvert

Objectif : Diagnostiquer un bug d'état persistant dans un layout et choisir le bon fichier spécial pour corriger le comportement voulu.

Contexte

Un utilisateur signale un bug étrange : sur /dashboard, il ouvre un menu déroulant useState défini dans app/dashboard/layout.tsx, puis clique sur "Analytics". Le menu déroulant reste ouvert alors qu'il s'attendait à ce qu'il se referme comme sur une navigation classique. L'équipe se demande si c'est un bug ou un comportement normal de l'App Router.

Résoudre l’exercice →