Changer le thème

Guide de démarrage Next.js App Router : concepts essentiels et utilisation de base

Easton editorial illustration: one large folder tree unfolding into a route map

La première fois que j’ai ouvert la documentation officielle Next.js, j’étais perdu. Dans la barre latérale, « Pages Router » et « App Router » côte à côte, comme pour dire « à vous de choisir ». Sauf que la question reste : lequel choisir ? Quelle différence ? La doc ne répond pas clairement — au contraire, plus on lit, plus c’est confus. Les tutoriels utilisent tantôt le dossier pages, tantôt app, avec des syntaxes totalement différentes.

J’ai fini par comprendre : Next.js propose en réalité deux systèmes de routage distincts. L’ancien s’appelle Pages Router, stable et fiable, mais sans certaines nouveautés. Le nouveau, App Router, lancé avec la v13, stable en v13.4, est désormais la direction recommandée par l’équipe.

Vous vous demandez peut-être : « Dois-je apprendre App Router ? Encore une nouveauté compliquée ? »

Cet article vise à dissiper cette confusion. J’explique simplement les concepts essentiels d’App Router — Server Components, fichiers spéciaux, différences avec Pages Router. À la fin, vous serez prêt à démarrer sans trop de faux pas.

Qu’est-ce qu’App Router ? Pourquoi l’utiliser ?

En bref, App Router est le nouveau système de routage introduit par Next.js en v13. Il s’appuie sur la dernière avancée React — les Server Components — pour un design plus moderne et flexible.

Par rapport à Pages Router, trois avantages nets :

1. Meilleures performances
App Router utilise les composants serveur par défaut. La majorité du code s’exécute côté serveur ; le navigateur télécharge moins de JavaScript, les pages chargent plus vite. Selon le rapport Vercel 2024, plus de 60 % des applications Next.js de premier plan ont basculé vers App Router.

"Plus de 60 % des applications Next.js de premier plan ont basculé vers App Router"

2. Système de layouts plus flexible
Avec Pages Router, les layouts imbriqués étaient pénibles. App Router les gère avec un simple layout.js, sans re-render du layout lors du changement de page — une navigation fluide.

3. Gestion d’erreurs et états de chargement renforcées
Définissez une animation de chargement avec loading.js, capturez les erreurs et affichez une UI de repli avec error.js. Pages Router exigeait du code manuel ; App Router fournit des conventions prêtes à l’emploi.

Pages Router reste utilisable ?
Oui. Les deux systèmes coexistent, mais si vous apprenez Next.js aujourd’hui, je recommande App Router directement. C’est la direction officielle ; les scaffolds de nouveaux projets (dès v14.1.4) l’utilisent par défaut.

Routage par système de fichiers : du dossier à la page

Le concept central d’App Router : votre structure de dossiers est votre structure de routes.

Un exemple concret :

app/
├── page.js              # Accueil, correspond à /
├── about/
│   └── page.js          # Page À propos, correspond à /about
└── blog/
    ├── page.js          # Liste du blog, correspond à /blog
    └── [slug]/
        └── page.js      # Détail article, correspond à /blog/:slug

Points clés à retenir :

1. page.js est l’entrée de la route
Seuls les fichiers nommés page.js deviennent des pages accessibles. Les autres (layout.js, loading.js, etc.) sont des fichiers fonctionnels, non accessibles directement.

2. Routes dynamiques avec crochets
Pour une route dynamique /blog/hello-world, créez app/blog/[slug]/page.js ; le paramètre slug est passé automatement au composant :

// app/blog/[slug]/page.js
export default function BlogPost({ params }) {
  return <h1>Article : {params.slug}</h1>
}

3. Catch-all avec [...slug]
Pour des chemins multi-niveaux comme /docs/a/b/c, utilisez app/docs/[...slug]/page.js ; params.slug sera un tableau ['a', 'b', 'c'].

Comparaison avec Pages Router :
Pages Router utilisait pages/blog/[id].js. App Router passe à app/blog/[id]/page.js, avec un niveau de dossier supplémentaire. Pourquoi ? Laisser de la place à layout.js, loading.js et autres fichiers spéciaux par route.

Au début, cela peut sembler lourd ; une fois l’habitude prise, la structure du projet devient bien plus claire.

Server Components vs Client Components : concept central

C’est probablement le concept le plus déroutant d’App Router. J’ai mis du temps à m’y faire.

En une phrase : dans App Router, les composants s’exécutent par défaut côté serveur ; le navigateur n’intervient que lorsque l’interactivité est requise.

Server Component par défaut

Dans app/, les composants créés sont des Server Components par défaut. Ils sont rendus côté serveur, puis le HTML est envoyé au navigateur.

Avantages évidents :

  • Moins de JavaScript : le code ne part pas vers le navigateur, fichiers JS plus légers
  • Accès direct aux ressources backend : requêtes BDD, clés API — sans risque d’exposition
  • Premier rendu rapide : HTML prêt côté serveur, FCP (First Contentful Paint) plus court

Exemple typique de Server Component :

// app/products/page.js
// Server Component, s'exécute côté serveur
async function getProducts() {
  const res = await fetch('https://api.example.com/products')
  return res.json()
}

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

  return (
    <div>
      <h1>Liste des produits</h1>
      {products.map(p => (
        <div key={p.id}>{p.name}</div>
      ))}
    </div>
  )
}

Vous voyez ? async/await directement, sans useEffect ni getServerSideProps.

Quand utiliser un Client Component ?

Certaines situations exigent le navigateur :

  • Hooks React (useState, useEffect)
  • Interactions utilisateur (onClick, onChange)
  • APIs navigateur (localStorage, window)

Il faut alors un Client Component. Marquage simple : ajoutez 'use client' en tête de fichier :

// components/AddToCartButton.js
'use client'  // Marqué comme Client Component

import { useState } from 'react'

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

  return (
    <button onClick={() => setCount(count + 1)}>
      Ajouter au panier ({count})
    </button>
  )
}

Usage mixte : bonnes pratiques

Le vrai atout : combiner les deux types.

Sur une page produits :

  • Liste des produits en Server Component (données côté serveur, moins de JS)
  • Bouton d’ajout au panier en Client Component (interaction au clic)
// app/products/page.js (Server Component)
import AddToCartButton from '@/components/AddToCartButton' // Client Component

async function getProducts() {
  // Récupération côté serveur
}

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

  return (
    <div>
      <h1>Liste des produits</h1>
      {products.map(p => (
        <div key={p.id}>
          {p.name}
          <AddToCartButton productId={p.id} />
        </div>
      ))}
    </div>
  )
}

Règle à retenir : Server Component par défaut. 'use client' uniquement si l’interaction l’exige.

Ne mettez pas 'use client' partout dès le départ — autant ne pas utiliser App Router.

Fichiers spéciaux : un projet plus professionnel

App Router définit des noms de fichiers spéciaux — layout.js, loading.js, error.js, etc. Au premier abord, cela peut sembler contraignant ; en pratique, c’est très pratique.

layout.js : layout partagé

Le fichier spécial le plus courant. Il définit le layout d’un segment de route et enveloppe toutes les pages du même niveau et des sous-routes.

Par exemple, ajouter une barre de navigation et un pied de page à toute l’application :

// app/layout.js (layout racine)
export default function RootLayout({ children }) {
  return (
    <html lang="fr">
      <body>
        <nav>Barre de navigation</nav>
        <main>{children}</main>
        <footer>Pied de page</footer>
      </body>
    </html>
  )
}

Les layouts peuvent s’imbriquer :

app/
├── layout.js          # Layout global (nav + pied de page)
├── page.js            # Accueil
└── dashboard/
    ├── layout.js      # Layout tableau de bord (barre latérale)
    ├── page.js        # /dashboard
    └── settings/
        └── page.js    # /dashboard/settings

En passant de /dashboard à /dashboard/settings, le layout global et celui du tableau de bord ne se re-rendent pas — seul page.js change. Fluide.

loading.js : état de chargement

Plus besoin de gérer loading avec useState. Créez un loading.js ; App Router enveloppe automatiquement la page avec Suspense :

// app/dashboard/loading.js
export default function Loading() {
  return <div>Chargement...</div>
}

Pendant la récupération des données, le contenu de loading.js s’affiche automatiquement. C’est tout.

error.js : Error Boundary

Capture les erreurs de page et affiche une UI de repli :

// app/dashboard/error.js
'use client'  // Les Error Boundaries doivent être des Client Components

export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Erreur : {error.message}</h2>
      <button onClick={reset}>Réessayer</button>
    </div>
  )
}

Piège à connaître : error.js ne capture pas les erreurs du layout.js au même niveau. Limitation des Error Boundaries React — elles ne capturent que les erreurs des composants enfants, pas celles du même niveau ou des parents.

Pour capturer les erreurs de layout.js, placez error.js dans le répertoire parent, ou utilisez global-error.js à la racine.

not-found.js : page 404

Affichée quand la route n’existe pas :

// app/not-found.js
export default function NotFound() {
  return <h1>Page introuvable</h1>
}

Vous pouvez aussi déclencher un 404 depuis le code :

import { notFound } from 'next/navigation'

export default async function BlogPost({ params }) {
  const post = await getPost(params.slug)
  if (!post) notFound()  // Déclenche not-found.js

  return <article>{post.title}</article>
}

Hiérarchie des fichiers

Ces fichiers spéciaux suivent une hiérarchie fixe :

layout.js
├── loading.js  (frontière Suspense)
│   └── page.js
└── error.js    (frontière Error)

layout est à l’extérieur ; error.js ne l’enveloppe pas. loading.js gère le chargement, error.js les erreurs.

Comprendre cette hiérarchie évite bien des pièges.

Récupération de données : adieu getServerSideProps

Si vous avez utilisé Pages Router, vous avez écrit getServerSideProps ou getStaticProps. Honnêtement, ces API étaient peu intuitives — fonction exportée séparément, passage de données peu direct.

App Router simplifie tout cela.

async/await directement

Dans un Server Component, récupérez les données directement dans la fonction du composant :

// app/posts/page.js
async function getPosts() {
  const res = await fetch('https://api.example.com/posts')
  return res.json()
}

export default async function PostsPage() {
  const posts = await getPosts()

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

Du async/await classique, sans API spéciale.

Récupération parallèle

Vous pouvez aussi récupérer plusieurs sources en parallèle :

export default async function Dashboard() {
  // Récupération parallèle, sans blocage
  const [user, posts, stats] = await Promise.all([
    getUser(),
    getPosts(),
    getStats()
  ])

  return (
    <div>
      <h1>{user.name}</h1>
      <Posts data={posts} />
      <Stats data={stats} />
    </div>
  )
}

Cache et revalidation

Next.js met en cache automatiquement les requêtes fetch. Vous contrôlez la stratégie :

// Cache 60 secondes puis revalidation
fetch('https://api.example.com/data', {
  next: { revalidate: 60 }
})

// Pas de cache, données toujours fraîches
fetch('https://api.example.com/data', {
  cache: 'no-store'
})

Comparaison avec Pages Router :

  • Pages Router : getServerSideProps + getStaticProps, fonctions exportées séparément
  • App Router : async/await directement dans le composant

Beaucoup plus simple, non ?

Questions fréquentes des débutants

En apprenant App Router, j’ai fait pas mal d’erreurs. Voici les problèmes les plus courants pour vous éviter les mêmes pièges.

Question 1 : quand utiliser ‘use client’ ?

Confusion : les tutoriels mettent 'use client' partout ; difficile de savoir quand l’ajouter.

Solution :
Une règle simple — ne l’ajoutez pas par défaut, seulement si nécessaire.

Ajoutez 'use client' uniquement si :

  • Vous utilisez des hooks React (useState, useEffect, useContext)
  • Il y a des interactions utilisateur (onClick, onChange)
  • Vous utilisez des APIs navigateur (window, localStorage)

Sinon, abstenez-vous. Le Server Component performe mieux et accède directement aux ressources backend.

Question 2 : relation entre layout.js et page.js ?

Confusion : ces deux fichiers dans le même dossier — qui enveloppe qui ?

Solution :
layout.js enveloppe page.js et les sous-routes.

app/
├── layout.js       # Enveloppe toutes les pages ci-dessous
├── page.js         # Accueil, enveloppé par le layout ci-dessus
└── about/
    └── page.js     # Page À propos, aussi enveloppée par le layout ci-dessus

Lors d’un changement de page, layout.js ne se re-rend pas — seul page.js change. D’où l’absence de clignotement de la barre de navigation.

Question 3 : comment obtenir les paramètres d’une route dynamique ?

Confusion : vous avez créé [slug]/page.js mais ne savez pas récupérer la valeur de slug.

Solution :
Via la prop params :

// app/blog/[slug]/page.js
export default function BlogPost({ params }) {
  console.log(params.slug)  // La valeur de l'URL
  return <h1>Article : {params.slug}</h1>
}

Pour une route dynamique imbriquée, par exemple app/blog/[category]/[slug]/page.js :

export default function Post({ params }) {
  console.log(params.category, params.slug)
  return <h1>{params.category} - {params.slug}</h1>
}

Question 4 : error.js ne fonctionne pas ?

Confusion : vous avez créé error.js, mais les erreurs de layout ne sont pas capturées.

Solution :
error.js ne capture pas les erreurs du layout.js au même niveau. Limitation des Error Boundaries React.

Deux options pour capturer les erreurs de layout :

  1. Placer error.js dans le répertoire parent
  2. Utiliser global-error.js à la racine (doit inclure les balises <html> et <body>)
// app/global-error.js
'use client'

export default function GlobalError({ error, reset }) {
  return (
    <html>
      <body>
        <h2>Erreur globale : {error.message}</h2>
        <button onClick={reset}>Réessayer</button>
      </body>
    </html>
  )
}

Question 5 : faut-il migrer mon ancien projet ?

Confusion : App Router apporte tant de nouveautés — faut-il tout réécrire ?

Solution :
Pas de précipitation.

Pages Router et App Router coexistent. Vous pouvez :

  • Garder pages/ pour l’existant
  • Utiliser app/ pour le nouveau

Vercel confirme que Pages Router restera supporté à long terme, sans dépréciation.

Pour un nouveau projet, utilisez App Router directement. C’est l’avenir, l’écosystème s’enrichit.

Conclusion

Récapitulons rapidement les cinq concepts essentiels d’App Router :

  1. Routage par système de fichiers : la structure des dossiers = structure des routes, page.js est l’entrée
  2. Server Components : exécution serveur par défaut, meilleures performances
  3. Client Components : marquez avec 'use client' quand l’interactivité est requise
  4. Fichiers spéciaux : layout.js, loading.js, error.js pour un projet plus professionnel
  5. Récupération de données : async/await directement, adieu getServerSideProps

App Router est bien la direction future de Next.js. Vercel investit, la communauté suit. Si vous apprenez Next.js aujourd’hui, App Router est le bon choix.

Et maintenant ?

Mettez-vous au code. Créez un petit projet — un blog ou une app de tâches avec App Router. Les concepts ne valent rien sans pratique.

En cas de blocage, pas de panique — Pages Router et App Router coexistent ; si besoin, restez sur Pages Router et migrez progressivement.

Enfin, la doc officielle Next.js peut sembler dense, mais la section App Router est détaillée. Pour des questions précises, consultez-la ou cherchez sur GitHub Discussions.

Bon apprentissage !

FAQ

Quand faut-il utiliser 'use client' ?
Uniquement quand l'interactivité est nécessaire : hooks React (useState, useEffect), interactions utilisateur (onClick, onChange), APIs navigateur (window, localStorage). Par défaut, privilégiez le Server Component pour de meilleures performances.
Quelle est la relation entre layout.js et page.js ?
layout.js enveloppe page.js et les sous-routes. Lors d'un changement de page, layout.js ne se re-rend pas — seul page.js est mis à jour, ce qui évite le clignotement des éléments partagés comme la barre de navigation.
Comment obtenir les paramètres d'une route dynamique ?
Via la prop params du composant. Par exemple, dans app/blog/[slug]/page.js, utilisez { params } et accédez à params.slug pour la valeur slug de l'URL.
Pourquoi error.js ne peut-il pas capturer les erreurs de layout.js ?
C'est une limitation des Error Boundaries React : elles ne capturent que les erreurs des composants enfants, pas celles des composants frères ou parents. Pour capturer les erreurs de layout, placez error.js dans le répertoire parent ou utilisez global-error.js à la racine.
Faut-il migrer un ancien projet vers App Router ?
Pas immédiatement. Pages Router et App Router peuvent coexister : gardez pages/ pour l'existant, app/ pour le nouveau. Vercel s'engage à maintenir Pages Router à long terme. Pour un nouveau projet, utilisez directement App Router.
Quelles sont les principales différences entre App Router et Pages Router ?
App Router repose sur les Server Components, rendu côté serveur par défaut, meilleures performances ; routage par système de fichiers avec layouts imbriqués ; récupération de données via async/await direct. Pages Router privilégie les composants client et getServerSideProps pour les données.

11 min de lecture · Publié le: 18 déc. 2025 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog