← Retour aux articles
Framework Next.jsReactSSR

Next.js App Router : pourquoi ça change tout

· 5 min de lecture

Next.js s’est imposé comme le framework React de référence depuis des années, mais l’App Router n’est pas une mise à jour incrémentale. C’est une refonte de la manière dont les applications React sont structurées, rendues et livrées. Les Server Components sont le défaut, les layouts persistent entre les navigations, et le data fetching se fait là où il aurait toujours dû se faire : sur le serveur, à l’intérieur des composants qui en ont besoin.

Des layouts qui persistent vraiment

Le Pages Router forçait un re-render complet à chaque navigation. L’App Router introduit des layouts imbriqués qui survivent aux changements de route. Une sidebar, une barre de navigation ou un lecteur audio reste monté pendant que seul le contenu de la page change.

export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="dashboard">
      <Sidebar />
      <main>{children}</main>
    </div>
  );
}

Toutes les routes sous /dashboard partagent ce layout. Le composant Sidebar ne se démonte pas, ne perd pas son état, et ne refetch pas ses données lors de la navigation entre les pages du dashboard. Ce n’est pas un hack astucieux. C’est la primitive de routing.

Du data fetching sans cérémonie

Dans l’App Router, les Server Components récupèrent les données directement. Plus de getServerSideProps, plus de getStaticProps, plus de fonctions loader dans un export séparé. Le composant est la couche de données.

import { notFound } from 'next/navigation';

async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await db.post.findUnique({ where: { slug: params.slug } });

  if (!post) notFound();

  return (
    <article>
      <h1>{post.title}</h1>
      <time>{new Date(post.date).toLocaleDateString()}</time>
      <div>{post.content}</div>
    </article>
  );
}

export default BlogPost;

Le mot-clé async sur un composant semble encore étrange, mais la simplicité est indéniable. Pas de gestion d’état pour le chargement, pas d’error boundaries autour des appels fetch, pas de duplication entre ce que le serveur sait et ce que le client doit redécouvrir. Les données sont là quand le composant se rend, point final.

Server Actions : la fin des routes API

Les server actions permettent d’appeler des fonctions côté serveur directement depuis les composants client. Soumissions de formulaires, mutations en base de données, revalidation, tout se passe dans une seule fonction sans câbler un endpoint API.

'use server';

import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';

export async function publishPost(postId: string) {
  await db.post.update({
    where: { id: postId },
    data: { status: 'published' },
  });

  revalidatePath('/posts');
  redirect('/posts');
}
'use client';

import { publishPost } from '@/app/posts/actions';

function PublishButton({ postId }: { postId: string }) {
  return <button onClick={() => publishPost(postId)}>Publier</button>;
}

L’action s’exécute sur le serveur quel que soit l’endroit où elle est appelée. Le composant client importe une référence, pas l’implémentation. Ce pattern supprime une couche entière d’infrastructure dont la plupart des applications n’ont pas besoin.

Streaming et Suspense : rendu progressif

L’App Router streame le HTML vers le navigateur au fur et à mesure qu’il devient disponible. Les sources de données lentes ne bloquent pas la page entière. Enveloppez un composant dans Suspense et le reste de la page se rend immédiatement pendant que la partie lente se charge.

import { Suspense } from 'react';

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <QuickStats />
      <Suspense fallback={<TableSkeleton />}>
        <RevenueTable />
      </Suspense>
      <Suspense fallback={<ChartSkeleton />}>
        <AnalyticsChart />
      </Suspense>
    </div>
  );
}

QuickStats se rend instantanément. RevenueTable et AnalyticsChart arrivent en streaming à mesure que leurs données se résolvent. L’utilisateur voit une page utile immédiatement au lieu de fixer un spinner plein écran. Ce n’est pas de l’UI optimiste. C’est du rendu progressif réel au niveau du HTML.

Routes parallèles et interceptées

L’App Router introduit des patterns de routing qui étaient auparavant impossibles sans hacks côté client. Les routes parallèles rendent plusieurs pages simultanément dans le même layout. Les routes interceptées permettent d’afficher une modale en overlay lors de la navigation tout en conservant la page complète comme URL partageable.

import { Modal } from '@/components/modal';

export default function PhotoModal({ params }: { params: { id: string } }) {
  return (
    <Modal>
      <Photo id={params.id} />
    </Modal>
  );
}

Cliquer sur une photo dans un feed ouvre une modale. Rafraîchir la page charge la page photo complète. Partager l’URL fonctionne. Le système de routing gère la complexité que les développeurs géraient manuellement avec du state, des portals et de la synchronisation d’URL.

Le compromis de complexité

L’App Router est plus puissant que le Pages Router, et il est aussi plus complexe. Le modèle mental des Server Components, la frontière "use client", la sémantique de cache et la distinction entre rendu statique et dynamique demandent un vrai investissement en compréhension. Mais cette complexité existe parce que les problèmes sont complexes. Le Pages Router les cachait derrière des abstractions plus simples qui ne tenaient pas à l’échelle. L’App Router les expose et donne les outils pour les résoudre correctement.

Next.js avec l’App Router n’est pas la manière la plus simple de construire une app React. C’est la plus complète. Pour les équipes qui construisent des applications en production nécessitant performance, SEO et maintenabilité, cette complétude est ce qui compte.