← Retour au programme
Module 08

Next.js : le framework fullstack

Assemblez routing, rendu serveur, composants client et pages dynamiques dans une architecture d'application moderne.

Next.js structure votre application React en une véritable plateforme fullstack : système de routes basé sur les fichiers, rendu serveur par défaut, zones interactives côté client et layouts partagés. Ce module vous enseigne à organiser une application qui reste lisible en grandissant.

Module suivant →

Philosophie

Next.js évolue rapidement. Le contenu écrit de ce cours est la référence principale. Les ressources vidéo servent de complément visuel mais peuvent présenter des API antérieures.

Prérequis & Outils

  • Maîtriser les fondamentaux de React
  • Comprendre les composants et le routing web
Next.jsApp RouterServer ComponentsClient Components

Objectifs du module

  • Maîtriser le système de routing de l'App Router (page, layout, route dynamique)
  • Distinguer quand utiliser un Server Component et quand activer un Client Component
  • Structurer une application qui reste maintenable à mesure qu'elle grandit

Ressource complémentaire

Next.js pour les débutants

Ressource complémentaire pour découvrir Next.js.

Contenu du module

Progression pas à pas.

Chaque leçon s'appuie sur la précédente avec un rythme pensé pour laisser de l'espace à la compréhension, à la pratique et à la révision.

Leçon 1

Pages, layouts et routes dynamiques

Comprendre comment l'arborescence de fichiers et de dossiers détermine les URLs de votre application.

Dans Next.js (App Router), les dossiers définissent les segments d'URL et les fichiers spéciaux leur donnent un rôle précis : page.tsx expose une route publique, layout.tsx partage une interface entre plusieurs pages, loading.tsx affiche un état de chargement pendant le rendu serveur, et error.tsx capture les erreurs d'un segment. Cette convention remplace un système de configuration centralisé par une structure de fichiers lisible et intuitive.

Une route dynamique, créée en nommant un dossier [slug], permet de réutiliser une seule page pour plusieurs contenus différents : /cours/python, /cours/react et /cours/nextjs partagent le même composant. IMPORTANT : dans Next.js 16, le paramètre params est une Promise et doit être attendu avec await. On écrit donc : const { slug } = await params. Cela permet au framework d'optimiser le rendu en résolvant les paramètres de manière asynchrone.

Les nested layouts (layouts imbriqués) sont l'un des concepts les plus puissants de l'App Router. Chaque segment de route peut définir son propre layout, et ces layouts s'emboîtent automatiquement. Par exemple, un layout à la racine gère la navigation principale, tandis qu'un layout dans /dashboard ajoute une barre latérale sans dupliquer le header. Cette architecture encourage la réutilisation et maintient chaque fichier focalisé sur une seule responsabilité.

Exemple : une route dynamique qui lit un paramètre slug (Next.js 16 — params est une Promise).tsx
// app/cours/[slug]/page.tsx
import { getCourse } from "@/lib/data";

export default async function CoursePage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  // Dans Next.js 16, params est une Promise
  const { slug } = await params;
  const course = await getCourse(slug);

  return (
    <article>
      <h1>{course.title}</h1>
      <p>{course.description}</p>
    </article>
  );
}

Exercice pratique

Exercice pratique

Concevez la structure de fichiers d'un blog Next.js avec une page d'accueil, une page listant les articles, et une page dynamique pour chaque article.

  1. Dessinez l'arborescence du dossier app/ avec les fichiers page.tsx nécessaires.
  2. Ajoutez un layout.tsx racine contenant un header avec navigation.
  3. Créez le dossier [slug] pour les articles dynamiques avec son page.tsx.
  4. Ajoutez un fichier loading.tsx dans le segment des articles pour gérer le chargement.

Vous avez réussi si votre arborescence couvre les trois routes et si vous avez pensé à utiliser await sur params dans la page dynamique.

Leçon 2

Server Components vs Client Components

Distinguer clairement quand un composant doit s'exécuter sur le serveur et quand il nécessite le client.

Par défaut, tous les composants dans l'App Router sont des Server Components. Ils s'exécutent uniquement sur le serveur, ce qui signifie qu'ils peuvent accéder directement à la base de données, utiliser des clés API secrètes, et ne jamais envoyer leur code JavaScript au navigateur. Le résultat est un bundle plus léger et un affichage initial plus rapide pour l'utilisateur.

Un Client Component est activé en ajoutant la directive "use client" en première ligne du fichier. Vous en avez besoin uniquement quand le composant utilise de l'interactivité (onClick, onChange), des hooks React (useState, useEffect), ou des API du navigateur (localStorage, window). La règle d'or : gardez le maximum de composants côté serveur et ne passez en client que les parties interactives.

Le streaming avec Suspense permet d'afficher progressivement l'interface pendant que les données se chargent. Au lieu d'attendre que toute la page soit prête, le serveur envoie d'abord le shell HTML (layout, navigation) puis remplit les zones de contenu au fur et à mesure. Le fichier loading.tsx crée automatiquement une frontière Suspense autour du segment de route correspondant.

Exemple : un Server Component qui charge des données, combiné avec un Client Component interactif.tsx
// app/cours/[slug]/page.tsx — Server Component (par défaut)
import { getCourse } from "@/lib/data";
import LikeButton from "./like-button";

export default async function CoursePage({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const course = await getCourse(slug);

  return (
    <article>
      <h1>{course.title}</h1>
      <p>{course.description}</p>
      {/* Ce composant a besoin d'interactivité → Client */}
      <LikeButton courseId={course.id} />
    </article>
  );
}

// app/cours/[slug]/like-button.tsx — Client Component
// "use client"
// import { useState } from "react";
//
// export default function LikeButton({ courseId }: { courseId: string }) {
//   const [liked, setLiked] = useState(false);
//   return (
//     <button onClick={() => setLiked(!liked)}>
//       {liked ? "❤️ Aimé" : "🤍 Aimer"}
//     </button>
//   );
// }

Exercice pratique

Exercice pratique

Vous avez une page qui affiche une liste de cours et un bouton de recherche filtrant les résultats. Identifiez quels composants doivent être des Server Components et lesquels des Client Components.

  1. Listez les composants de la page : header, liste de cours, carte de cours, barre de recherche, bouton de filtre.
  2. Pour chaque composant, déterminez s'il a besoin d'interactivité, de hooks ou d'API navigateur.
  3. Marquez chaque composant comme Server ou Client et justifiez votre choix.

Vous avez réussi si vous avez correctement identifié que seuls les composants interactifs nécessitent "use client" et que le chargement des données reste côté serveur.

Leçon 3

Patterns de données et routes API

Maîtriser les différentes stratégies pour lire et écrire des données dans une application Next.js.

Les Server Actions sont des fonctions marquées avec "use server" qui s'exécutent sur le serveur mais peuvent être appelées directement depuis un formulaire ou un composant client. Elles remplacent la nécessité de créer manuellement des endpoints API pour les mutations de données. Un formulaire avec action={maServerAction} envoie les données directement au serveur sans écrire de fetch côté client.

Les Route Handlers (fichiers route.ts dans app/api/) restent utiles pour créer des API REST classiques consommées par des clients externes, des webhooks ou des applications mobiles. Ils exportent des fonctions nommées GET, POST, PUT, DELETE correspondant aux méthodes HTTP. Pour les interactions internes de votre application, préférez les Server Actions qui sont plus simples et typées.

La revalidation contrôle quand les données mises en cache sont rafraîchies. revalidatePath('/cours') invalide le cache d'une page spécifique après une mutation, tandis que revalidateTag('courses') invalide toutes les requêtes associées à ce tag. Le composant Link de Next.js gère la navigation côté client avec prefetching automatique, offrant des transitions instantanées entre les pages.

Exemple : un formulaire de contact utilisant une Server Action.tsx
// app/contact/page.tsx
import { submitContact } from "./actions";

export default function ContactPage() {
  return (
    <form action={submitContact}>
      <label htmlFor="name">Nom</label>
      <input id="name" name="name" required />

      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" required />

      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" required />

      <button type="submit">Envoyer</button>
    </form>
  );
}

// app/contact/actions.ts
// "use server";
//
// import { revalidatePath } from "next/cache";
//
// export async function submitContact(formData: FormData) {
//   const name = formData.get("name") as string;
//   const email = formData.get("email") as string;
//   const message = formData.get("message") as string;
//
//   await saveToDatabase({ name, email, message });
//   revalidatePath("/contact");
// }

Exercice pratique

Exercice pratique

Construisez un formulaire de contact complet avec une Server Action qui valide les données, les enregistre et redirige l'utilisateur.

  1. Créez une page app/contact/page.tsx avec un formulaire (nom, email, message).
  2. Créez un fichier app/contact/actions.ts avec une Server Action marquée "use server".
  3. Dans l'action, validez que tous les champs sont remplis et que l'email est valide.
  4. Après la sauvegarde, utilisez revalidatePath et redirect pour rediriger vers une page de confirmation.

Vous avez réussi si votre Server Action valide les champs, enregistre les données côté serveur, et redirige l'utilisateur sans aucun fetch() côté client.

Projet final

Blog personnel

Construisez un blog personnel complet avec Next.js utilisant l'App Router, des routes dynamiques pour les articles, des layouts imbriqués, et un mélange de Server et Client Components.

Critères de réussite

  • L'application utilise l'App Router avec au moins 3 routes : accueil, liste des articles, article individuel.
  • Les articles utilisent une route dynamique [slug] avec params correctement attendu (await).
  • Un layout racine contient la navigation et un layout imbriqué est utilisé pour la section blog.
  • Au moins un Client Component est utilisé de manière justifiée (recherche, like, thème).
  • Un fichier loading.tsx est présent pour au moins un segment de route.

Guide de réalisation

  • 1Commencez par créer l'arborescence de fichiers complète avant d'écrire le contenu des composants.
  • 2Utilisez des données en dur (tableau d'objets) pour les articles — pas besoin de base de données pour ce projet.
  • 3Pensez à la séparation serveur/client : chargez les données dans les Server Components, déléguez l'interactivité aux Client Components.
  • 4Testez la navigation entre les pages et vérifiez que les layouts persistent correctement.

Consolidez vos acquis avant de poursuivre.

Prenez le temps de revoir les concepts difficiles et de refaire les exercices qui méritent une seconde passe.