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

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/awaitdirectement 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 :
- Placer
error.jsdans le répertoire parent - 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 :
- Routage par système de fichiers : la structure des dossiers = structure des routes,
page.jsest l’entrée - Server Components : exécution serveur par défaut, meilleures performances
- Client Components : marquez avec
'use client'quand l’interactivité est requise - Fichiers spéciaux :
layout.js,loading.js,error.jspour un projet plus professionnel - Récupération de données :
async/awaitdirectement, adieugetServerSideProps
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' ?
Quelle est la relation entre layout.js et page.js ?
Comment obtenir les paramètres d'une route dynamique ?
Pourquoi error.js ne peut-il pas capturer les erreurs de layout.js ?
Faut-il migrer un ancien projet vers App Router ?
Quelles sont les principales différences entre App Router et Pages Router ?
11 min de lecture · Publié le: 18 déc. 2025 · Mis à jour le: 27 juil. 2026
Guide complet Next.js
Vous lisez le premier article de cette série. Continuez avec le suivant ou ouvrez le hub de la série pour voir tout le parcours.
Précédent
Vous êtes au début de cette série.
Suivant
Next.js 15 en pratique : comment j'ai construit un blog de niveau production en un week-end
Cas pratique Next.js 15 + Server Actions + Prisma : guide pas à pas pour construire un blog full-stack de niveau production en un week-end. Code complet, pièges rencontrés et stratégies d'optimisation des performances.
Partie 2 sur 51



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire