Structurez avec des layouts partagés et routes imbriquées

Structurez votre application avec des layouts

Actuellement, notre application Shiny fonctionne très bien : nous avons des pages pour le Dashboard, les Employés et les Projets, ainsi que des pages de détails pour chaque employé et chaque projet. Félicitations !

Si vous regardez maintenant le fichier  main.jsx  , vous remarquerez que le composant  Header  est placé en dehors du Routes. Cette structure permet d’afficher le même header sur toutes les pages de l’application.

Mais cette approche montre rapidement ses limites. Que se passerait-il si certaines sections de l’application devaient avoir une structure différente ? Par exemple, une partie publique avec un Header simple, et une partie d’administration avec un Header et une sidebar.

La réponse : les routes imbriquées, associées au composantOutlet  !

Dans ce chapitre, nous allons restructurer notre application afin de définir des layouts différents selon les sections, tout en conservant une navigation maintenable et flexible. Alors, partons à la découverte des routes imbriquées !

Découvrez les routes imbriquées

Le concept de routes parent/enfant

Les routes imbriquées (nested routes en anglais) permettent de créer une hiérarchie de routes : une route parent contient plusieurs routes enfants. C'est comme un dossier qui contient des sous-dossiers dans votre système de fichiers. 

Prenons un exemple concret. Imaginez une section "À propos" avec plusieurs sous-pages :

/about          # → Présentation générale
/about/team     # → L'équipe
/about/values   # → Nos valeurs
/about/contact  # → Contact

Toutes ces routes partagent le même préfixe  /about  et pourraient partager le même layout (une navigation secondaire, par exemple). Les routes imbriquées permettent d'organiser ce genre de structure de manière élégante.

Le problème actuel dans notre code

Ouvrez votre fichier  main.jsx. Vous verrez cette structure :

<Router>
    <Header />
        <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/employees" element={<Employees />} />
            <Route path="/employees/:id" element={<EmployeeDetail />} />
            {/* ... */}
        </Routes>
</Router>

Le Header est en dehors du système de routing. Cela signifie qu'il est toujours affiché, sur toutes les pages. C'est bien pour notre application actuelle, mais c'est limitant :

  • Impossible d'avoir des pages sans Header

  • Impossible d'avoir des Headers différents selon les sections

  • Le Header n'est pas considéré comme faisant partie du layout par React Router

Comment résoudre ça ?

Avec le composant Outlet

Utilisez le composant Outlet

Qu'est-ce que Outlet ?

Le composant Outlet  est un emplacement dans lequel React Router affiche le contenu de la route enfant qui correspond à l’URL courante.

Concrètement, une route parente définit souvent un layout (par exemple un header, une sidebar, etc.), et les routes enfants définissent le contenu qui change à l’intérieur de ce layout.

Imaginez un cadre de tableau : le cadre représente le layout (défini par la route parente), et l’image à l’intérieur correspond à la route enfant affichée dans Outlet.

Outlet  est importé depuis react-router-dom:

import { Outlet } from 'react-router-dom'

Créez un composant Layout

Nous allons créer un composant  Layout  qui contient le Header et un Outlet pour le contenu.

Créez le dossier  /src/components/Layout  et ajoutez-y le fichierindex.jsx  :

import { Outlet } from 'react-router-dom'
import Header from '../Header'
import './Layout.css'

function Layout() {
  return (
    <div className="layout">
      <Header />
      <main className="layout-content">
        <Outlet />
      </main>
    </div>
  )
}

export default Layout

Explications :

  • Ligne 1 : Import du composant Outlet

  • Ligne 8 : Le Header est affiché en haut

  • Ligne 10 : L'Outlet affichera le contenu de la route enfant active

Créez maintenant le fichier  Layout.css  dans le même dossier : 

.layout {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.layout-content {
  flex: 1;
}

Ces styles assurent que le layout prend toute la hauteur de l'écran et que le contenu principal occupe l'espace disponible.

Refactorisez le router avec des routes imbriquées

Maintenant, transformons notre router pour utiliser le Layout comme route parent.

Ouvrez  main.jsx  et importez Layout :

import Layout from './components/Layout'

Puis modifiez la structure des routes. Maintenant, nous avons :

    <Router>
      <Routes>
        <Route element={<Layout />}>
          <Route path="/" element={<Home />} />
          <Route path="/projects" element={<Projects />} />
          <Route path="/projects/:id" element={<ProjectDetail />} />
          <Route path="/employees" element={<Employees />} />
          {/* .... */}
        </Route>
      </Routes>
    </Router>

Les changements :

  • Ligne 2 : Header retiré (il est maintenant dans Layout)

  • Ligne 3 : Route parent avec  element={<Layout />}  mais sans path

  • Lignes 4-10 : Toutes les routes sont imbriquées dans la route Layout

Testez votre application avec npm run dev. Tout fonctionne exactement comme avant, mais le code est maintenant mieux structuré ! Le Header fait partie du système de routing.

Utilisez NavLink pour la navigation active

Notre Header fonctionne bien, mais il manque un élément important pour l'expérience utilisateur : l'indication visuelle de la page active. Actuellement, impossible de savoir sur quelle page on se trouve juste en regardant la navigation.

Le composant NavLink

React Router fournit le composant  NavLink, qui est comme Link  mais avec une fonctionnalité supplémentaire : il ajoute automatiquement la classe CSS  active  au lien qui correspond à la route actuelle.

Importez-le dans votre Header :

import { NavLink } from 'react-router-dom'

Puis remplacez tous vos Link  par des  NavLink :

 <nav className="nav">
    <NavLink to="/" className="nav-link">Dashboard</NavLink>
    <NavLink to="/projects" className="nav-link">Projets</NavLink>
    <NavLink to="/employees" className="nav-link">Employés</NavLink>
    <NavLink to="/about" className="nav-link">À propos</NavLink>
</nav>

Maintenant, ajoutez le style pour les liens actifs dans  Header.css  :

.nav-link.active {
  color: #646cff;
  font-weight: 700;
  border-bottom: 3px solid #646cff;
}

Testez dans votre navigateur : le lien de la page actuelle est maintenant mis en évidence ! C'est beaucoup plus clair pour l'utilisateur.

Petit problème : Si vous cliquez sur "Dashboard" puis "Employés", vous remarquerez que "Dashboard" reste actif.

Pourquoi ?

Parce que /  est inclus dans/employees. React Router considère que / est actif pour toutes les routes.

La solution : ajoutez la prop  end  au NavLink du Dashboard :

<Navlink to="/" end="" classname="nav-link">Dashboard</Navlink>

La propend  indique que le lien n'est actif que si l'URL correspond exactement au path, sans rien après. Problème résolu !

Créez des routes imbriquées pour une section

Maintenant que vous maîtrisez les bases, voyons comment créer une vraie section avec routes imbriquées.

Cas d'usage : Section "À propos" avec sous-pages

Actuellement,  /about  affiche une simple page.

Et si nous voulions plusieurs sous-pages ?

  • /about  → Présentation de Shiny

  • /about/team  → L'équipe

  • /about/contact  → Contact

Créez trois fichiers dans  /src/pages/About/  :

Presentation.jsx :

function Presentation() {
  return (
    <div className="page">
      <h1>À propos de Shiny ✨</h1>
      <p>Shiny est une entreprise innovante spécialisée dans le développement web.</p>
    </div>
  )
}

export default Presentation

Team.jsx :

function Team() {
  return (
    <div className="page">
      <h1>Notre équipe 👥</h1>
      <p>Une équipe passionnée de 10 personnes.</p>
    </div>
  )
}

export default Team

Contact.jsx :

function Contact() {
  return (
    <div className="page">
      <h1>Contactez-nous 📧</h1>
      <p>Email : contact@shiny.com</p>
    </div>
  )
}

export default Contact

Configurez les routes imbriquées

Dans main.jsx, importez les nouveaux composants :

import AboutPresentation from './pages/About/Presentation'
import AboutTeam from './pages/About/Team'
import AboutContact from './pages/About/Contact'

Puis modifiez la route  /about  pour la transformer en route parent :

<Route path="/about" element={<AboutLayout />}>
    <Route index element={<AboutPresentation />} />
    <Route path="team" element={<AboutTeam />} />
    <Route path="contact" element={<AboutContact />} />
</Route>

Nouveauté<Route index>

La route index  est la route par défaut quand on accède au path parent. Ici,  /about  afficheraAboutPresentation. C'est équivalent à  path=""  mais plus explicite et recommandé.

Créez /src/pages/About/Layout.jsx :

import { Outlet, NavLink } from 'react-router-dom'
import './AboutLayout.css'

function AboutLayout() {
  return (
    <div className="page">
      <nav className="about-nav">
        <NavLink to="/about" end className="about-link">Présentation</NavLink>
        <NavLink to="/about/team" className="about-link">Équipe</NavLink>
        <NavLink to="/about/contact" className="about-link">Contact</NavLink>
      </nav>
      <div className="about-content">
        <Outlet />
      </div>
    </div>
  )
}

export default AboutLayout

Créez le CSS  AboutLayout.css  :

.about-nav {
  display: flex;
  gap: 1rem;
  margin-bottom: 2rem;
  border-bottom: 2px solid rgba(255, 255, 255, 0.1);
  padding-bottom: 1rem;
}

.about-link {
  padding: 0.5rem 1rem;
  color: #ccc;
  text-decoration: none;
  border-radius: 4px;
  transition: all 0.2s ease;
}

.about-link.active {
  background: #646cff;
  color: #fff;
}

Nous avons maintenant une nouvelle navigation pour la section À propos de notre site !

Page
Notre nouvelle navigation pour la page À propos.

Testez dans votre navigateur : vous avez maintenant une section complète avec navigation secondaire ! Cliquez sur "Équipe" ou "Contact", la navigation secondaire reste affichée et le contenu change. C'est exactement le comportement d'une application professionnelle.

Structure finale :

Layout  # (Header global)
   └─ /about            # → AboutLayout (navigation secondaire)
    ├─ index            # → Presentation
    ├─ team             # → Team
    └─ contact          # → Contact

Vous avez deux niveaux d'imbrication : le Layout global contient AboutLayout, qui contient les pages de contenu. Chaque niveau a son propre Outlet qui affiche le niveau suivant. C'est la puissance des routes imbriquées !

À vous de jouer !

C'est maintenant le moment de voler de vos propres ailes et d'appliquer ces concepts par vous-même ! Vous retrouverez le code dans la branche P1C4-Begin

Exercice 1 - Section Projets avec filtres

Contexte : Actuellement /projects  affiche tous les projets. Vous voulez ajouter des filtres par statut avec une navigation secondaire.

Objectifs :

  1. Créer un ProjectsLayout

    • Créez  /src/pages/Projects/Layout.jsx

    • Ajoutez une navigation secondaire avec NavLink : "Tous", "En cours", "Terminés", "Planifiés"

    • Ajoutez un Outlet pour le contenu

    • Stylisez la navigation avec  ProjectsLayout.css

  2. Créer les pages de filtres

    • /src/pages/Projects/All.jsx  → Tous les projets

    • /src/pages/Projects/InProgress.jsx  → Projets "En cours"

    • /src/pages/Projects/Completed.jsx  → Projets "Terminés"

    • /src/pages/Projects/Planned.jsx  → Projets "Planifiés"

  3. Configurer les routes dans main.jsx

    • Transformez la route  /projects  en route parent avec  ProjectsLayout

    • Ajoutez les routes enfants : index (All),in-progresscompletedplanned

    • Importez tous les composants nécessaires

  4. Filtrer les données

    • Utilisez  getProjectsByStatus()  déjà disponible dans  data/projects.js

    • Dans chaque composant, récupérez les projets filtrés

    • Affichez-les avec le composant ProjectCard

  5. Utiliser NavLink correctement

    • Ajoutez  end  sur le lien "Tous" pour éviter qu'il soit toujours actif

    • Stylisez les liens actifs différemment

Découvrez la solution sur la branche P1C4-Solution-1

Exercice 2 - Bonus : Navigation "breadcrumb"

Objectif avancé : Ajoutez un fil d'Ariane (breadcrumb) dans le Layout qui affiche le chemin de navigation.

Exemple :

  • Sur  /employees  → "Dashboard / Employés"

  • Sur  /employees/5  → "Dashboard / Employés / Sophie Martin"

  • Sur  /about/team  → "Dashboard / À propos / Équipe"

La solution se trouve dans la branche P1C4-Solution-2.

En résumé

  • Les routes imbriquées permettent de créer une hiérarchie de routes avec des routes parents et enfants.

  • Le composant Outlet affiche le contenu de la route enfant correspondante dans le layout parent.

  • Les layouts partagés (comme notre composant  Layout  ) évitent la répétition de code et structurent l'application.

  • Une route index (<Route index>) est la route par défaut d'une route parent, équivalent à  path="".

  • NavLink ajoute automatiquement la classe CSS  active  au lien actif pour améliorer l'expérience utilisateur.

  • La prop  end  sur NavLink permet de contrôler précisément quand un lien est considéré actif.

Vous maîtrisez maintenant l'organisation avancée de votre application avec React Router ! Votre code est plus structuré, plus maintenable, et votre application offre une meilleure expérience utilisateur. 

Félicitations ! Vous avez terminé ce cours sur les bases de React Router. Il est temps maintenant de valider les compétences acquises au travers d'un quiz !

Et si vous obteniez un diplôme OpenClassrooms ?
  • Formations jusqu’à 100 % financées
  • Date de début flexible
  • Projets professionnalisants
  • Mentorat individuel
Trouvez la formation et le financement faits pour vous