Qu’est-ce que Next.js ?
Next.js est un framework React qui transforme une bibliothèque d’interface utilisateur en une plateforme d’application complète. Il ajoute le routage basé sur le système de fichiers, le rendu côté serveur, la récupération de données, des points de terminaison API, l’optimisation des images et des polices, ainsi qu’un environnement d’exécution serveur. Au lieu de devoir choisir et configurer manuellement un routeur, une couche de données et un système de build, vous disposez d’une pile cohérente conçue pour fonctionner ensemble.
Maintenu par Vercel, il est devenu la méthode par défaut pour construire des applications React en production. Le modèle actuel est l’App Router, basé sur les React Server Components, qui modifie l’endroit où votre code s’exécute et la manière dont les données circulent.
L’App Router et le routage basé sur le système de fichiers
Les routes sont définies par le système de fichiers. Un dossier correspond à un segment d’URL, et un fichier page.tsx le transforme en route.
app/
├── layout.tsx # root layout, wraps everything
├── page.tsx # /
├── about/page.tsx # /about
├── blog/
│ ├── page.tsx # /blog
│ └── [slug]/page.tsx # /blog/:slug
└── dashboard/
├── layout.tsx # layout for /dashboard/*
└── page.tsx # /dashboard
Les segments dynamiques utilisent des crochets, et les layouts s’imbriquent afin que l’interface utilisateur partagée reste montée lors de la navigation. Des fichiers spéciaux tels que loading.tsx, error.tsx et not-found.tsx gèrent les états correspondants sans code supplémentaire.
// app/blog/[slug]/page.tsx
export default async function PostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPost(slug);
return <article>{post.content}</article>;
}
Composants serveur et client
C’est le concept qui définit le Next.js moderne. Par défaut, les composants sont des server components : ils sont rendus sur le serveur, peuvent lire des données et des secrets directement, et n’ajoutent rien au bundle du navigateur. Les composants qui nécessitent de l’interactivité doivent utiliser "use client".
// app/counter.tsx
"use client";
import { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
Un server component peut importer et rendre un client component, mais l’inverse n’est pas possible. La règle d’or : gardez vos composants sur le serveur et poussez "use client" vers la feuille interactive la plus petite possible. Cela permet de limiter la quantité de JavaScript envoyée au navigateur.
Récupération de données
Dans l’App Router, vous récupérez les données dans les server components à l’aide de la fonction standard fetch, enrichie d’options de mise en cache et de revalidation.
// app/posts/page.tsx
async function getPosts() {
const res = await fetch("https://api.example.com/posts", {
next: { revalidate: 60 },
});
if (!res.ok) throw new Error("Failed to load posts");
return res.json();
}
export default async function PostsPage() {
const posts = await getPosts();
return <PostList posts={posts} />;
}
Comme le code s’exécute sur le serveur, il n’y a pas d’indicateur de chargement (loading spinner) à configurer ni d’effet de cascade (waterfall) côté client. Vous pouvez déclencher la revalidation par intervalle de temps, à la demande avec revalidatePath ou revalidateTag, ou désactiver complètement la mise en cache. Les requêtes fetch sont également dédupliquées au sein d’un même rendu.
Route handlers et server actions
Pour les points de terminaison API, les route handlers exportent des fonctions qui reçoivent un web Request et retournent un Response.
// app/api/posts/route.ts
export async function GET() {
const posts = await db.post.findMany();
return Response.json(posts);
}
export async function POST(request: Request) {
const body = await request.json();
const post = await db.post.create({ data: body });
return Response.json(post, { status: 201 });
}
Pour les mutations provenant de votre propre interface utilisateur, les server actions sont généralement plus simples. Marquez une fonction avec "use server" et appelez-la depuis un formulaire ou un gestionnaire d’événements.
// app/posts/new/page.tsx
import { revalidatePath } from "next/cache";
async function createPost(formData: FormData) {
"use server";
await db.post.create({ data: { title: String(formData.get("title")) } });
revalidatePath("/posts");
}
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" required />
<button type="submit">Create</button>
</form>
);
}
Les server actions gèrent ensemble la validation, les écritures en base de données et la revalidation du cache, et le formulaire fonctionne même avant le chargement de JavaScript. C’est l’amélioration progressive (progressive enhancement) sans effort supplémentaire.
Rendu et mise en cache
Next.js prend en charge plusieurs stratégies de rendu au sein d’une même application :
- Les pages statiques sont pré-rendues lors de l’étape de build.
- Les pages rendues côté serveur sont générées à chaque requête.
- Le streaming envoie le HTML par morceaux afin que les données lentes ne bloquent pas l’affichage de la structure (shell).
- Le rendu côté client gère les îlots interactifs après l’hydratation.
La mise en cache comporte plusieurs couches, du cache de fetch au cache complet des routes. C’est là que se situe la principale courbe d’apprentissage, et les paramètres par défaut sont choisis pour maximiser la rapidité. Lorsque vous constatez que des données sont obsolètes, la solution consiste généralement à effectuer un appel de revalidation explicite plutôt qu’à désactiver la mise en cache partout.
Optimisation intégrée
Next.js propose des composants qui gèrent les tâches de performance les plus courantes :
next/imageredimensionne, charge en différé (lazy-load) et sert des formats modernes.next/fontauto-héberge les polices et élimine le layout shift.next/scriptcontrôle la manière dont les scripts tiers sont chargés.- Turbopack accélère le développement et les builds de production.
Grâce à ces configurations par défaut, une application Next.js bien conçue est rapide sans nécessiter de projet d’optimisation distinct.
Bonnes pratiques
- Gardez vos composants sur le serveur et utilisez
"use client"uniquement là où c’est nécessaire. - Récupérez vos données dans les composants serveur plutôt que dans des effets.
- Utilisez les server actions pour les mutations et effectuez une revalidation explicite.
- Colocalisez vos routes, composants et accès aux données sous
app/. - Ajoutez
loading.tsxeterror.tsxpour garantir une bonne UX aux frontières de vos routes. - Privilégiez les composants d’image et de police du framework plutôt qu’une gestion manuelle.
- Comprenez le fonctionnement du cache avant de le désactiver ; privilégiez une revalidation ciblée.
Erreurs courantes
- Parsemer
"use client"en haut de l’arborescence et perdre ainsi les avantages du serveur. - Effectuer des fetches dans
useEffectalors qu’un server component suffirait. - Oublier de revalider après une mutation et afficher des données obsolètes.
- Supposer que les API de l’App Router et du Pages Router sont interchangeables.
- Bloquer une route à cause de données lentes au lieu d’utiliser le streaming avec Suspense.
- Considérer Next.js comme un simple outil front-end et reconstruire un backend séparé.
Et après ?
Next.js est aujourd’hui la solution la plus complète pour déployer des applications React. Renforcez vos bases en React et TypeScript, comparez son modèle avec Remix et Astro, et apprenez à maîtriser le runtime Node.js sur lequel il est déployé. Ensuite, créez une petite application full-stack intégrant un server component, un route handler et une server action.