Français

Découvrez la puissance de l'App Router de Next.js avec notre guide détaillé sur le routage basé sur les fichiers. Apprenez à structurer votre application, créer des routes dynamiques, gérer les mises en page, et plus encore.

App Router de Next.js : Un Guide Complet sur le Routage Basé sur les Fichiers

L'App Router de Next.js, introduit dans Next.js 13 et devenu la norme dans les versions ultérieures, révolutionne la manière dont nous structurons et naviguons dans les applications. Il introduit un système de routage basé sur les fichiers puissant et intuitif qui simplifie le développement, améliore les performances et enrichit l'expérience globale du développeur. Ce guide complet explorera en profondeur le routage basé sur les fichiers de l'App Router, vous fournissant les connaissances et les compétences nécessaires pour construire des applications Next.js robustes et évolutives.

Qu'est-ce que le Routage Basé sur les Fichiers ?

Le routage basé sur les fichiers est un système où la structure des routes de votre application est directement déterminée par l'organisation de vos fichiers et répertoires. Dans l'App Router de Next.js, vous définissez les routes en créant des fichiers dans le répertoire `app`. Chaque dossier représente un segment de route, et des fichiers spéciaux à l'intérieur de ces dossiers définissent comment ce segment de route sera traité. Cette approche offre plusieurs avantages :

Démarrer avec l'App Router

Pour utiliser l'App Router, vous devez créer un nouveau projet Next.js ou migrer un projet existant. Assurez-vous d'utiliser la version 13 de Next.js ou une version ultérieure.

Créer un Nouveau Projet :

Vous pouvez créer un nouveau projet Next.js avec l'App Router en utilisant la commande suivante :

npx create-next-app@latest my-app --example with-app

Migrer un Projet Existant :

Pour migrer un projet existant, vous devez déplacer vos pages du répertoire `pages` vers le répertoire `app`. Vous devrez peut-être ajuster votre logique de routage en conséquence. Next.js fournit un guide de migration pour vous aider dans ce processus.

Concepts Fondamentaux du Routage Basé sur les Fichiers

L'App Router introduit plusieurs fichiers spéciaux et conventions qui définissent la manière dont vos routes sont gérées :

1. Le Répertoire `app`

Le répertoire `app` est la racine des routes de votre application. Tous les fichiers et dossiers à l'intérieur de ce répertoire seront utilisés pour générer des routes. Tout ce qui se trouve en dehors du répertoire `app` (comme le répertoire `pages` si vous migrez) sera ignoré par l'App Router.

2. Le Fichier `page.js`

Le fichier `page.js` (ou `page.jsx`, `page.ts`, `page.tsx`) est la partie la plus fondamentale de l'App Router. Il définit le composant d'interface utilisateur qui sera rendu pour un segment de route spécifique. C'est un fichier requis pour tout segment de route que vous souhaitez rendre directement accessible.

Exemple :

Si vous avez une structure de fichiers comme celle-ci :

app/
  about/
    page.js

Le composant exporté depuis `app/about/page.js` sera rendu lorsqu'un utilisateur naviguera vers `/about`.

// app/about/page.js
import React from 'react';

export default function AboutPage() {
  return (
    <div>
      <h1>À Propos de Nous</h1>
      <p>Apprenez-en plus sur notre entreprise.</p>
    </div>
  );
}

3. Le Fichier `layout.js`

Le fichier `layout.js` (ou `layout.jsx`, `layout.ts`, `layout.tsx`) définit une interface utilisateur qui est partagée entre plusieurs pages au sein d'un segment de route. Les mises en page sont utiles pour créer des en-têtes, des pieds de page, des barres latérales et d'autres éléments cohérents qui doivent être présents sur plusieurs pages.

Exemple :

Disons que vous souhaitez ajouter un en-tête à la fois à la page `/about` et à une hypothétique page `/about/team`. Vous pouvez créer un fichier `layout.js` dans le répertoire `app/about` :

// app/about/layout.js
import React from 'react';

export default function AboutLayout({ children }) {
  return (
    <div>
      <header>
        <h1>À Propos de Notre Entreprise</h1>
      </header>
      <main>{children}</main>
    </div>
  );
}

La prop `children` sera remplacée par l'interface utilisateur rendue par le fichier `page.js` dans le même répertoire ou dans des répertoires imbriqués.

4. Le Fichier `template.js`

Le fichier `template.js` est similaire à `layout.js`, mais il crée une nouvelle instance du composant pour chaque route enfant. C'est utile pour les scénarios où vous souhaitez maintenir l'état du composant ou éviter les re-rendus lors de la navigation entre les routes enfants. Contrairement aux mises en page, les templates se re-rendent à chaque navigation. L'utilisation de templates est idéale pour animer des éléments lors de la navigation.

Exemple :

// app/template.js
'use client'

import { useState } from 'react'

export default function Template({ children }) {
  const [count, setCount] = useState(0)

  return (
    <main>
      <p>Template : {count}</p>
      <button onClick={() => setCount(count + 1)}>Mettre à jour le Template</button>
      {children}
    </main>
  )
}

5. Le Fichier `loading.js`

Le fichier `loading.js` (ou `loading.jsx`, `loading.ts`, `loading.tsx`) vous permet de créer une interface de chargement qui s'affiche pendant le chargement d'un segment de route. C'est utile pour offrir une meilleure expérience utilisateur lors de la récupération de données ou de l'exécution d'autres opérations asynchrones.

Exemple :

// app/about/loading.js
import React from 'react';

export default function Loading() {
  return <p>Chargement des informations \"à propos\"...</p>;
}

Lorsqu'un utilisateur navigue vers `/about`, le composant `Loading` sera affiché jusqu'à ce que le composant `page.js` soit entièrement rendu.

6. Le Fichier `error.js`

Le fichier `error.js` (ou `error.jsx`, `error.ts`, `error.tsx`) vous permet de créer une interface d'erreur personnalisée qui s'affiche lorsqu'une erreur se produit dans un segment de route. C'est utile pour fournir un message d'erreur plus convivial et empêcher l'ensemble de l'application de planter.

Exemple :

// app/about/error.js
'use client'

import React from 'react';

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Une erreur est survenue !</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>Réessayer</button>
    </div>
  );
}

Si une erreur se produit lors du rendu de la page `/about`, le composant `Error` sera affiché. La prop `error` contient des informations sur l'erreur, et la fonction `reset` permet à l'utilisateur de tenter de recharger la page.

7. Groupes de Routes

Les Groupes de Routes `(nomDuGroupe)` vous permettent d'organiser vos routes sans affecter la structure de l'URL. Ils sont créés en enveloppant un nom de dossier entre parenthèses. C'est particulièrement utile pour organiser les mises en page et les composants partagés.

Exemple :

app/
  (marketing)/
    about/
      page.js
    contact/
      page.js
  (shop)/
    products/
      page.js

Dans cet exemple, les pages `about` et `contact` sont regroupées sous le groupe `marketing`, et la page `products` est sous le groupe `shop`. Les URL restent `/about`, `/contact` et `/products`, respectivement.

8. Routes Dynamiques

Les routes dynamiques vous permettent de créer des routes avec des segments variables. C'est utile pour afficher du contenu basé sur des données récupérées d'une base de données ou d'une API. Les segments de route dynamiques sont définis en enveloppant le nom du segment entre crochets (par exemple, `[id]`).

Exemple :

Disons que vous voulez créer une route pour afficher des articles de blog individuels en fonction de leur ID. Vous pouvez créer une structure de fichiers comme celle-ci :

app/
  blog/
    [id]/
      page.js

Le segment `[id]` est un segment dynamique. Le composant exporté depuis `app/blog/[id]/page.js` sera rendu lorsqu'un utilisateur naviguera vers une URL comme `/blog/123` ou `/blog/456`. La valeur du paramètre `id` sera disponible dans la prop `params` du composant.

// app/blog/[id]/page.js
import React from 'react';

export default async function BlogPost({ params }) {
  const { id } = params;

  // Récupérer les données pour l'article de blog avec l'ID donné
  const post = await fetchBlogPost(id);

  if (!post) {
    return <p>Article de blog non trouvé.</p>;
  }

  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </div>
  );
}

async function fetchBlogPost(id) {
  // Simuler la récupération de données depuis une base de données ou une API
  return new Promise((resolve) => {
    setTimeout(() => {
      const posts = {
        '123': { title: 'Mon Premier Article de Blog', content: 'Ceci est le contenu de mon premier article de blog.' },
        '456': { title: 'Un Autre Article de Blog', content: 'Voici un autre contenu passionnant.' },
      };
      resolve(posts[id] || null);
    }, 500);
  });
}

Vous pouvez également utiliser plusieurs segments dynamiques dans une route. Par exemple, vous pourriez avoir une route comme `/blog/[category]/[id]`.

9. Segments Attrape-Tout

Les segments attrape-tout (catch-all) vous permettent de créer des routes qui correspondent à n'importe quel nombre de segments. C'est utile pour des scénarios comme la création d'un CMS où la structure de l'URL est déterminée par l'utilisateur. Les segments attrape-tout sont définis en ajoutant trois points avant le nom du segment (par exemple, `[...slug]`).

Exemple :

app/
  docs/
    [...slug]/
      page.js

Le segment `[...slug]` correspondra à n'importe quel nombre de segments après `/docs`. Par exemple, il correspondra à `/docs/getting-started`, `/docs/api/users` et `/docs/advanced/configuration`. La valeur du paramètre `slug` sera un tableau contenant les segments correspondants.

// app/docs/[...slug]/page.js
import React from 'react';

export default function DocsPage({ params }) {
  const { slug } = params;

  return (
    <div>
      <h1>Docs</h1>
      <p>Slug : {slug ? slug.join('/') : 'Pas de slug'}</p>
    </div>
  );
}

Les segments attrape-tout optionnels peuvent être créés en ajoutant le nom du segment entre doubles crochets `[[...slug]]`. Cela rend le segment de route optionnel. Exemple :

app/
  blog/
    [[...slug]]/
      page.js

Cette configuration rendra le composant page.js à la fois pour `/blog` et pour `/blog/n/importe/quel/nombre/de/segments`.

10. Routes Parallèles

Les Routes Parallèles vous permettent de rendre simultanément une ou plusieurs pages dans la même mise en page. C'est particulièrement utile pour les mises en page complexes, comme les tableaux de bord, où différentes sections de la page peuvent être chargées indépendamment. Les Routes Parallèles sont définies en utilisant le symbole `@` suivi d'un nom de slot (par exemple, `@sidebar`, `@main`).

Exemple :

app/
  @sidebar/
    page.js  // Contenu pour la barre latérale
  @main/
    page.js  // Contenu pour la section principale
  default.js // Requis : Définit la mise en page par défaut pour les routes parallèles

Le fichier `default.js` est requis lors de l'utilisation de routes parallèles. Il définit comment les différents slots sont combinés pour créer la mise en page finale.

// app/default.js
export default function RootLayout({ children: { sidebar, main } }) {
  return (
    <div style={{ display: 'flex' }}>
      <aside style={{ width: '200px', backgroundColor: '#f0f0f0' }}>
        {sidebar}
      </aside>
      <main style={{ flex: 1, padding: '20px' }}>
        {main}
      </main>
    </div>
  );
}

11. Routes d'Interception

Les Routes d'Interception vous permettent de charger une route depuis une autre partie de votre application dans la mise en page actuelle. C'est utile pour créer des modales, des galeries d'images et d'autres éléments d'interface utilisateur qui doivent apparaître par-dessus le contenu de la page existante. Les Routes d'Interception sont définies en utilisant la syntaxe `(..)`, qui indique de combien de niveaux il faut remonter dans l'arborescence des répertoires pour trouver la route interceptée.

Exemple :

app/
  (.)photos/
    [id]/
      page.js  // La route interceptée
  feed/
    page.js  // La page où la modale de la photo est affichée

Dans cet exemple, lorsqu'un utilisateur clique sur une photo dans la page `/feed`, la route `app/(.)photos/[id]/page.js` est interceptée et affichée comme une modale par-dessus la page `/feed`. La syntaxe `(.)` indique à Next.js de regarder un niveau au-dessus (dans le répertoire `app`) pour trouver la route `photos/[id]`.

Récupération de Données avec l'App Router

L'App Router offre un support intégré pour la récupération de données en utilisant les Composants Serveur et les Composants Client. Les Composants Serveur sont rendus sur le serveur, tandis que les Composants Client sont rendus sur le client. Cela vous permet de choisir la meilleure approche pour chaque composant en fonction de ses besoins.

Composants Serveur

Les Composants Serveur sont la valeur par défaut dans l'App Router. Ils vous permettent de récupérer des données directement dans vos composants sans avoir besoin de routes d'API distinctes. Cela peut améliorer les performances et simplifier votre code.

Exemple :

// app/products/page.js
import React from 'react';

export default async function ProductsPage() {
  const products = await fetchProducts();

  return (
    <div>
      <h1>Produits</h1>
      <ul>
        {products.map((product) => (
          <li key={product.id}>{product.name}</li>
        ))}
      </ul>
    </div>
  );
}

async function fetchProducts() {
  // Simuler la récupération de données depuis une base de données ou une API
  return new Promise((resolve) => {
    setTimeout(() => {
      const products = [
        { id: 1, name: 'Produit A' },
        { id: 2, name: 'Produit B' },
        { id: 3, name: 'Produit C' },
      ];
      resolve(products);
    }, 500);
  });
}

Dans cet exemple, la fonction `fetchProducts` est appelée directement dans le composant `ProductsPage`. Le composant est rendu sur le serveur, et les données sont récupérées avant que le HTML ne soit envoyé au client.

Composants Client

Les Composants Client sont rendus sur le client et vous permettent d'utiliser des fonctionnalités côté client comme les écouteurs d'événements, l'état et les API du navigateur. Pour utiliser un Composant Client, vous devez ajouter la directive `'use client'` en haut du fichier.

Exemple :

// app/counter/page.js
'use client'

import React, { useState } from 'react';

export default function CounterPage() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>Compteur</h1>
      <p>Compteur : {count}</p>
      <button onClick={() => setCount(count + 1)}>Incrémenter</button>
    </div>
  );
}

Dans cet exemple, le composant `CounterPage` est un Composant Client car il utilise le hook `useState`. La directive `'use client'` indique à Next.js de rendre ce composant sur le client.

Techniques de Routage Avancées

L'App Router offre plusieurs techniques de routage avancées qui peuvent être utilisées pour créer des applications complexes et sophistiquées.

1. Gestionnaires de Route (Route Handlers)

Les Gestionnaires de Route vous permettent de créer des points de terminaison d'API dans votre répertoire `app`. Cela élimine le besoin d'un répertoire `pages/api` séparé. Les Gestionnaires de Route sont définis dans des fichiers nommés `route.js` (ou `route.ts`) et exportent des fonctions qui gèrent différentes méthodes HTTP (par exemple, `GET`, `POST`, `PUT`, `DELETE`).

Exemple :

// app/api/users/route.js
import { NextResponse } from 'next/server'

export async function GET(request) {
  // Simuler la récupération d'utilisateurs depuis une base de données
  const users = [
    { id: 1, name: 'John Doe' },
    { id: 2, name: 'Jane Doe' },
  ];

  return NextResponse.json(users);
}

export async function POST(request) {
  const body = await request.json()
  console.log('Données reçues :', body)
  return NextResponse.json({ message: 'Utilisateur créé' }, { status: 201 })
}

Cet exemple définit un gestionnaire de route à `/api/users` qui gère à la fois les requêtes `GET` et `POST`. La fonction `GET` renvoie une liste d'utilisateurs, et la fonction `POST` crée un nouvel utilisateur.

2. Groupes de Routes avec Plusieurs Mises en Page

Vous pouvez combiner des groupes de routes avec des mises en page pour créer différentes mises en page pour différentes sections de votre application. C'est utile pour les scénarios où vous souhaitez avoir un en-tête ou une barre latérale différente pour différentes parties de votre site.

Exemple :

app/
  (marketing)/
    layout.js  // Mise en page marketing
    about/
      page.js
    contact/
      page.js
  (admin)/
    layout.js  // Mise en page admin
    dashboard/
      page.js

Dans cet exemple, les pages `about` et `contact` utiliseront la mise en page `marketing`, tandis que la page `dashboard` utilisera la mise en page `admin`.

3. Middleware

Le Middleware vous permet d'exécuter du code avant qu'une requête ne soit traitée par votre application. C'est utile pour des tâches comme l'authentification, l'autorisation, la journalisation et la redirection des utilisateurs en fonction de leur emplacement ou de leur appareil.

Le Middleware est défini dans un fichier nommé `middleware.js` (ou `middleware.ts`) à la racine de votre projet.

Exemple :

// middleware.js
import { NextResponse } from 'next/server'

export function middleware(request) {
  // Vérifier si l'utilisateur est authentifié
  const isAuthenticated = false; // Remplacer par votre logique d'authentification

  if (!isAuthenticated && request.nextUrl.pathname.startsWith('/admin')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return NextResponse.next();
}

// Voir "Chemins de correspondance" ci-dessous pour en savoir plus
export const config = {
  matcher: '/admin/:path*',
}

Cet exemple définit un middleware qui vérifie si l'utilisateur est authentifié avant de lui permettre d'accéder à toute route sous `/admin`. Si l'utilisateur n'est pas authentifié, il est redirigé vers la page `/login`.

Meilleures Pratiques pour le Routage Basé sur les Fichiers

Pour tirer le meilleur parti du système de routage basé sur les fichiers de l'App Router, considérez les meilleures pratiques suivantes :

Exemples d'Internationalisation avec l'App Router de Next.js

L'App Router de Next.js simplifie l'internationalisation (i18n) grâce au routage basé sur les fichiers. Voici comment vous pouvez implémenter l'i18n efficacement :

1. Routage par sous-chemin

Organisez vos routes en fonction de la locale en utilisant des sous-chemins. Par exemple :

app/
  [locale]/
    page.tsx         // Page d'accueil pour la locale
    about/
      page.tsx     // Page "À propos" pour la locale
// app/[locale]/page.tsx
import { getTranslations } from './dictionaries';

export default async function HomePage({ params: { locale } }) {
  const t = await getTranslations(locale);
  return (<h1>{t.home.title}</h1>);
}

// dictionaries.js
const dictionaries = {
  en: () => import('./dictionaries/en.json').then((module) => module.default),
  es: () => import('./dictionaries/es.json').then((module) => module.default),
};

export const getTranslations = async (locale) => {
  try {
    return dictionaries[locale]() ?? dictionaries.en();
  } catch (error) {
    console.error(`Échec du chargement des traductions pour la locale ${locale}`, error);
    return dictionaries.en();
  }
};

Dans cette configuration, le segment de route dynamique `[locale]` gère différentes locales (par exemple, `/en`, `/es`). Les traductions sont chargées dynamiquement en fonction de la locale.

2. Routage par domaine

Pour une approche plus avancée, vous pouvez utiliser différents domaines ou sous-domaines pour chaque locale. Cela implique souvent une configuration supplémentaire avec votre hébergeur.

3. Middleware pour la Détection de la Locale

Utilisez un middleware pour détecter automatiquement la locale préférée de l'utilisateur et le rediriger en conséquence.

// middleware.js
import { NextResponse } from 'next/server';
import { match } from '@formatjs/intl-localematcher';
import Negotiator from 'negotiator';

let locales = ['en', 'es', 'fr'];

function getLocale(request) {
  const negotiatorHeaders = {};
  request.headers.forEach((value, key) => (negotiatorHeaders[key] = value));
  let languages = new Negotiator({ headers: negotiatorHeaders }).languages();

  try {
      return match(languages, locales, 'en'); // Utiliser "en" comme locale par défaut
  } catch (error) {
      console.error("Erreur de correspondance de locale :", error);
      return 'en'; // Revenir à l'anglais si la correspondance échoue
  }
}

export function middleware(request) {
  const pathname = request.nextUrl.pathname;
  const pathnameIsMissingLocale = locales.every(
    (locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
  );

  if (pathnameIsMissingLocale) {
    const locale = getLocale(request);

    return NextResponse.redirect(
      new URL(
        `/${locale}${pathname.startsWith('/') ? '' : '/'}${pathname}`,
        request.url
      )
    );
  }
}

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

Ce middleware vérifie si le chemin demandé a un préfixe de locale. Si ce n'est pas le cas, il détecte la locale préférée de l'utilisateur à l'aide de l'en-tête `Accept-Language` et le redirige vers le chemin approprié spécifique à la locale. Des bibliothèques comme `@formatjs/intl-localematcher` et `negotiator` sont utilisées pour gérer la négociation de la locale.

App Router de Next.js et Accessibilité Globale

La création d'applications web globalement accessibles nécessite une attention particulière aux principes d'accessibilité (a11y). L'App Router de Next.js fournit une base solide pour créer des expériences accessibles, mais il est essentiel de mettre en œuvre les meilleures pratiques pour garantir que votre application soit utilisable par tous, quelles que soient leurs capacités.

Considérations Clés en Matière d'Accessibilité

  1. HTML Sémantique : Utilisez des éléments HTML sémantiques (par exemple, `<article>`, `<nav>`, `<aside>`, `<main>`) pour structurer votre contenu. Cela donne un sens aux technologies d'assistance et aide les utilisateurs à naviguer plus facilement sur votre site.
  2. Attributs ARIA : Utilisez les attributs ARIA (Accessible Rich Internet Applications) pour améliorer l'accessibilité des composants et widgets personnalisés. Les attributs ARIA fournissent des informations supplémentaires sur le rôle, l'état et les propriétés des éléments aux technologies d'assistance.
  3. Navigation au Clavier : Assurez-vous que tous les éléments interactifs sont accessibles via le clavier. Les utilisateurs doivent pouvoir naviguer dans votre application à l'aide de la touche `Tab` et interagir avec les éléments à l'aide des touches `Entrée` ou `Espace`.
  4. Contraste des Couleurs : Utilisez un contraste de couleur suffisant entre le texte et l'arrière-plan pour assurer la lisibilité pour les utilisateurs malvoyants. Les Directives pour l'accessibilité des contenus Web (WCAG) recommandent un rapport de contraste d'au moins 4.5:1 pour le texte normal et 3:1 pour le texte de grande taille.
  5. Texte Alternatif pour les Images : Fournissez un texte alternatif descriptif pour toutes les images. Le texte alternatif fournit une alternative textuelle pour les images qui peut être lue par les lecteurs d'écran.
  6. Étiquettes de Formulaire : Associez les étiquettes de formulaire à leurs champs de saisie correspondants à l'aide de l'élément `<label>`. Cela permet aux utilisateurs de savoir clairement quelles informations sont attendues dans chaque champ.
  7. Test avec Lecteur d'Écran : Testez votre application avec un lecteur d'écran pour vous assurer qu'elle est accessible aux utilisateurs malvoyants. Les lecteurs d'écran populaires incluent NVDA, JAWS et VoiceOver.

Implémenter l'Accessibilité dans l'App Router de Next.js

  1. Utiliser le Composant Link de Next.js : Utilisez le composant `<Link>` pour la navigation. Il offre des fonctionnalités d'accessibilité intégrées, telles que le pré-chargement et la gestion du focus.
  2. Gestion du Focus : Lors de la navigation entre les pages ou de l'ouverture de modales, assurez-vous que le focus est correctement géré. Le focus doit être placé sur l'élément le plus logique de la nouvelle page ou de la modale.
  3. Composants Personnalisés Accessibles : Lors de la création de composants personnalisés, assurez-vous qu'ils sont accessibles en suivant les principes décrits ci-dessus. Utilisez du HTML sémantique, des attributs ARIA et la navigation au clavier pour rendre vos composants utilisables par tous.
  4. Linting et Tests : Utilisez des outils de linting comme ESLint avec des plugins d'accessibilité pour identifier les problèmes d'accessibilité potentiels dans votre code. Utilisez également des outils de test automatisés pour tester les violations d'accessibilité de votre application.

Conclusion

Le système de routage basé sur les fichiers de l'App Router de Next.js offre un moyen puissant et intuitif de structurer et de naviguer dans vos applications. En comprenant les concepts fondamentaux et les meilleures pratiques décrits dans ce guide, vous pouvez créer des applications Next.js robustes, évolutives et maintenables. Expérimentez avec les différentes fonctionnalités de l'App Router et découvrez comment il peut simplifier votre flux de travail de développement et améliorer l'expérience utilisateur.