Cours Next.js · Module 2: Routing et layouts

Interface de chargement et layout.tsx

7 min de lecture
Dans cette leçon6

À Metropolis Quantum, chaque district possède une infrastructure permanente - éclairage, systèmes de navigation, panneaux d'information - qui reste active quel que soit celui qui se trouve actuellement dans la zone. Lorsqu'un habitant se déplace entre les secteurs, des écrans holographiques affichent une animation de chargement tandis que l'infrastructure environnante reste inchangée.

Dans l'App Router de Next.js 16, ces concepts ont leurs équivalents : layout.tsx est l'infrastructure permanente qui englobe les pages et n'est pas re-rendue lors de la navigation, et loading.tsx est l'indicateur de chargement automatique affiché pendant les transitions de page.

Qu'est-ce que layout.tsx ?

Le fichier layout.tsx est l'un des fichiers spéciaux les plus importants de l'App Router. Il définit une mise en page partagée qui englobe les pages d'un segment donné et tous ses sous-segments. La caractéristique la plus importante d'une mise en page : elle n'est pas re-rendue lors de la navigation entre des pages qui partagent la même mise en page. C'est comme l'infrastructure d'un district Quantum - elle reste en place, seul le contenu à l'intérieur change.

Root Layout - la mise en page principale obligatoire

Toute application Next.js doit avoir un Root Layout dans le fichier app/layout.tsx. C'est la seule mise en page qui doit contenir les balises <html> et <body> :

1// app/layout.tsx - Root Layout
2import Link from 'next/link';
3
4export default function RootLayout({
5  children,
6}: {
7  children: React.ReactNode
8}) {
9  return (
10    <html lang="fr">
11      <body>
12        <nav className="quantum-nav">
13          <Link href="/">Accueil</Link>
14          <Link href="/dashboard">Dashboard</Link>
15          <Link href="/settings">Paramètres</Link>
16        </nav>
17        <main>{children}</main>
18        <footer>Metropolis Quantum © 2150</footer>
19      </body>
20    </html>
21  );
22}

Les liens dans les layouts sont des composants Link de next/link. Un simple <a href> rechargerait toute la page : le layout serait remonté et perdrait son état.

Le Root Layout est rendu une seule fois et reste actif pendant toute la durée de vie de l'application. La navigation entre /dashboard et /settings ne changera que le contenu {children} - la navigation et le pied de page resteront en place sans être re-rendus.

Mises en page imbriquées

Les mises en page peuvent être imbriquées à n'importe quel niveau de la structure des dossiers. Chaque segment de chemin peut avoir son propre layout.tsx :

1app/
2  ├── layout.tsx              # Root Layout (html + body + navigation)
3  ├── page.tsx                # Accueil /
4  └── dashboard/
5      ├── layout.tsx          # Dashboard Layout (barre latérale + panneau)
6      ├── page.tsx            # /dashboard
7      ├── analytics/
8      │   └── page.tsx        # /dashboard/analytics
9      └── settings/
10          └── page.tsx        # /dashboard/settings

Le Dashboard Layout s'imbrique à l'intérieur du Root Layout :

1// app/dashboard/layout.tsx - Mise en page imbriquée
2import Link from 'next/link';
3
4export default function DashboardLayout({
5  children,
6}: {
7  children: React.ReactNode
8}) {
9  return (
10    <div className="dashboard-container">
11      <aside className="sidebar">
12        <h2>Panneau Quantum</h2>
13        <ul>
14          <li><Link href="/dashboard">Aperçu</Link></li>
15          <li><Link href="/dashboard/analytics">Analytique</Link></li>
16          <li><Link href="/dashboard/settings">Paramètres</Link></li>
17        </ul>
18      </aside>
19      <section className="content">
20        {children}
21      </section>
22    </div>
23  );
24}

Lorsque l'utilisateur navigue de /dashboard vers /dashboard/analytics, le Root Layout et le Dashboard Layout ne sont pas re-rendus. Seul le {children} du Dashboard Layout change. C'est une optimisation de performance cruciale - l'état des composants de mise en page (par ex. barre latérale ouverte, position de défilement) est préservé.

Préserver l'état de la mise en page pendant la navigation

L'un des plus grands avantages des mises en page est la préservation de l'état pendant la navigation. Imagine un panneau de contrôle à Metropolis Quantum - quand tu passes d'une section à l'autre, tu ne veux pas que toute l'interface se recharge :

1// app/dashboard/layout.tsx
2'use client';
3
4import Link from 'next/link';
5import { useState } from 'react';
6
7export default function DashboardLayout({
8  children,
9}: {
10  children: React.ReactNode
11}) {
12  // Cet état SURVIVRA à la navigation entre les pages /dashboard/* !
13  const [sidebarOpen, setSidebarOpen] = useState(true);
14
15  return (
16    <div className="flex">
17      {sidebarOpen && (
18        <aside className="w-64 bg-gray-900 p-4">
19          <h2>Panneau Quantum</h2>
20          <nav>
21            <Link href="/dashboard">Aperçu</Link>
22            <Link href="/dashboard/analytics">Analytique</Link>
23          </nav>
24        </aside>
25      )}
26      <div className="flex-1">
27        <button onClick={() => setSidebarOpen(!sidebarOpen)}>
28          {sidebarOpen ? 'Masquer' : 'Afficher'} la barre latérale
29        </button>
30        {children}
31      </div>
32    </div>
33  );
34}

Si l'utilisateur ferme la barre latérale sur la page /dashboard et navigue vers /dashboard/analytics, la barre latérale restera fermée - l'état sidebarOpen n'est pas réinitialisé.

loading.tsx - états de chargement automatiques

Le fichier loading.tsx est un fichier spécial que Next.js affiche automatiquement en repli pendant le chargement du contenu de la page. C'est comme un écran de chargement holographique à Metropolis Quantum - il apparaît automatiquement lorsque le système prépare les données.

Comment fonctionne loading.tsx ?

1// app/dashboard/loading.tsx
2export default function DashboardLoading() {
3  return (
4    <div className="loading-screen">
5      <div className="spinner"></div>
6      <p>Chargement des données du panneau Quantum...</p>
7    </div>
8  );
9}

Next.js englobe automatiquement page.tsx dans une frontière <Suspense>, en utilisant loading.tsx comme repli :

1// Voici ce que Next.js fait « en coulisses » :
2<Layout>
3  <Suspense fallback={<Loading />}>
4    <Page />
5  </Suspense>
6</Layout>

Grâce à cela :

  • La mise en page s'affiche immédiatement (elle n'attend pas les données de la page)
  • Le composant de chargement apparaît automatiquement pendant le chargement
  • La page remplace le composant de chargement lorsque les données sont prêtes
  • L'utilisateur voit une interface réactive au lieu d'un écran vide

loading.tsx à différents niveaux

Chaque segment peut avoir son propre loading.tsx :

1app/
2  ├── loading.tsx                # Chargement pour la page d'accueil
3  ├── dashboard/
4  │   ├── loading.tsx            # Chargement pour /dashboard/*
5  │   ├── analytics/
6  │   │   └── loading.tsx        # Chargement spécifique à l'analytique
7  │   └── settings/
8  │       └── page.tsx           # Utilisera le loading de /dashboard/

template.tsx vs layout.tsx

Next.js propose un autre fichier spécial - template.tsx. Il ressemble exactement à layout.tsx, mais avec une différence cruciale : le template est remonté (une nouvelle instance est créée) à chaque navigation.

1// app/dashboard/template.tsx
2// Nouvelle instance à CHAQUE navigation !
3export default function DashboardTemplate({
4  children,
5}: {
6  children: React.ReactNode
7}) {
8  return (
9    <div className="animate-fadeIn">
10      {children}
11    </div>
12  );
13}

Quand utiliser template.tsx plutôt que layout.tsx ?

Caractéristiquelayout.tsxtemplate.tsx
Re-renduNON - préserve l'étatOUI - nouvelle instance
État des composantsPréservé entre les navigationsRéinitialisé à la navigation
useEffectNe se réexécute pasS'exécute à chaque navigation
Animations d'entréeNe se répètent pasSe répètent à chaque entrée

Cas d'usage de template.tsx :

  • Animations d'entrée/sortie des pages (fondu à chaque navigation)
  • Journalisation des vues de page (useEffect avec analytique à chaque entrée)
  • Formulaires qui doivent se réinitialiser à la navigation

Applications pratiques

1// app/dashboard/layout.tsx
2'use client';
3
4import Link from 'next/link';
5import { usePathname } from 'next/navigation';
6
7export default function DashboardLayout({
8  children,
9}: {
10  children: React.ReactNode
11}) {
12  const pathname = usePathname();
13
14  const links = [
15    { href: '/dashboard', label: 'Aperçu' },
16    { href: '/dashboard/analytics', label: 'Analytique' },
17    { href: '/dashboard/settings', label: 'Paramètres' },
18  ];
19
20  return (
21    <div className="dashboard">
22      <nav className="dashboard-nav">
23        {links.map((link) => (
24          <Link
25            key={link.href}
26            href={link.href}
27            className={pathname === link.href ? 'active' : ''}
28          >
29            {link.label}
30          </Link>
31        ))}
32      </nav>
33      <main>{children}</main>
34    </div>
35  );
36}

Résumé

Les mises en page et l'interface de chargement sont des concepts fondamentaux de l'App Router dans Next.js 16 :

  1. layout.tsx - une mise en page partagée qui englobe les pages et n'est pas re-rendue lors de la navigation. Elle préserve l'état des composants.
  2. Root Layout - la mise en page principale obligatoire avec les balises <html> et <body>.
  3. Mises en page imbriquées - chaque segment peut avoir sa propre mise en page, créant une hiérarchie de mises en page.
  4. loading.tsx - un indicateur de chargement automatique. Next.js englobe la page dans <Suspense> avec loading comme repli.
  5. template.tsx - comme une mise en page, mais crée une nouvelle instance à chaque navigation. Idéal pour les animations et la journalisation.

Ces mécanismes, inspirés de l'infrastructure permanente des districts de Metropolis Quantum, permettent de construire des interfaces efficaces et réactives où l'utilisateur ne voit jamais d'écran vide, et où la navigation est fluide et instantanée.

Code de cette leçon : app/layout.tsx
1// Root Layout - Metropolis Quantum
2import { ReactNode } from 'react';
3import Link from 'next/link';
4
5interface RootLayoutProps {
6  children: ReactNode;
7}
8
9export const metadata = {
10  title: 'Metropolis Quantum',
11  description: 'Loading UI et layout.tsx',
12};
13
14export default function RootLayout({ children }: RootLayoutProps) {
15  return (
16    <html lang="fr">
17      <body style={{ margin: 0, fontFamily: 'monospace', background: '#0f0f23', color: '#e0e0e0' }}>
18        <nav style={{
19          display: 'flex',
20          gap: '16px',
21          padding: '12px 20px',
22          background: '#1a1a2e',
23          borderBottom: '1px solid #64ffda',
24        }}>
25          <Link href="/" style={{ color: '#64ffda', textDecoration: 'none' }}>Accueil</Link>
26          <Link href="/dashboard" style={{ color: '#bd93f9', textDecoration: 'none' }}>Dashboard</Link>
27          <Link href="/settings" style={{ color: '#ff79c6', textDecoration: 'none' }}>Paramètres</Link>
28        </nav>
29        <main style={{ padding: '20px' }}>
30          {children}
31        </main>
32        <footer style={{
33          textAlign: 'center',
34          padding: '12px',
35          borderTop: '1px solid #16213e',
36          color: '#8892b0',
37          fontSize: '12px',
38        }}>
39          Metropolis Quantum &copy; 2150
40        </footer>
41      </body>
42    </html>
43  );
44}

Tu as repéré une erreur dans cette leçon ?

Articles utiles