Routes dynamiques avec [slug]
Une route dynamique capture une valeur variable dans l’URL. En App Router, on place le nom du paramètre entre crochets dans le nom du dossier : [slug], [id], [username]…
app/
└── blog/
└── [slug]/
└── page.tsx → /blog/mon-article
→ /blog/nextjs-16
→ /blog/n-importe-quel-slug
Dans page.tsx, récupérez le paramètre via les params. Dans Next.js 16, les params sont une Promise — il faut les await :
import { notFound } from 'next/navigation';
export default async function PostPage({
params,
}: {
params: Promise<{ slug: string }>; // Promise dans Next.js 16
}) {
const { slug } = await params; // obligatoire : await params
const post = await getPostBySlug(slug);
if (!post) notFound(); // affiche not-found.tsx
return (
<article className="max-w-3xl mx-auto py-12 px-4">
<h1 className="text-4xl font-bold mb-6">{post.title}</h1>
<p className="text-gray-600 leading-relaxed">{post.content}</p>
</article>
);
}
Dans les versions précédentes, params était un objet synchrone. Dans Next.js 16, c’est une Promise — vous devez obligatoirement faire const { slug } = await params. Si vous migrez un projet, le codemod officiel (npx @next/codemod upgrade latest) s’en charge automatiquement.
generateStaticParams — pré-générer les routes
Exportez generateStaticParams pour que Next.js pré-génère les pages dynamiques au build. Les pages sont servies instantanément depuis un CDN sans aucun calcul à la requête.
// Appelé au build — retourne tous les slugs à pré-générer
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}
// Métadonnées dynamiques pour le SEO
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPostBySlug(slug);
return {
title: post?.title,
description: post?.excerpt,
};
}
Le composant Link — navigation client
Utilisez toujours <Link> de next/link pour naviguer entre les pages. Contrairement à <a>, il ne recharge pas la page et déclenche le prefetching automatique au survol.
import Link from 'next/link';
export default async function BlogPage() {
const posts = await getPosts();
return (
<ul className="space-y-4">
{posts.map((post) => (
<li key={post.slug}>
<Link
href={`/blog/${post.slug}`}
className="block p-4 border rounded-xl hover:border-gray-400 transition-colors"
>
<h2 className="font-semibold">{post.title}</h2>
<p className="text-sm text-gray-500 mt-1">{post.excerpt}</p>
</Link>
</li>
))}
</ul>
);
}
useRouter — navigation programmatique
Pour naviguer depuis du code (après la soumission d’un formulaire, après un clic sur un bouton), utilisez le hook useRouter dans un Client Component :
'use client';
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
import { useState } from 'react';
export default function SearchForm() {
const router = useRouter();
const pathname = usePathname(); // ex: "/blog"
const [query, setQuery] = useState('');
function handleSubmit(e: React.FormEvent) {
e.preventDefault();
router.push(`/search?q=${query}`); // navigation
}
return (
<form onSubmit={handleSubmit} className="flex gap-2">
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Rechercher..."
className="border rounded-xl px-4 py-2 flex-1"
/>
<button type="submit" className="bg-gray-900 text-white px-4 py-2 rounded-xl">
Chercher
</button>
</form>
);
}
Prefetching incrémental dans Next.js 16
Next.js 16 réécrit complètement le système de prefetching. Au lieu de précharger la page entière, il utilise le prefetching incrémental : seules les parties pas encore en cache sont téléchargées. Résultat : les navigations sont instantanées avec beaucoup moins de données transférées.
// Prefetch par défaut (au survol)
<Link href="/blog">Blog</Link>
// Désactiver le prefetch (contenu derrière auth)
<Link href="/dashboard" prefetch={false}>Dashboard</Link>
// Prefetch dès l'entrée dans le viewport (agressif)
<Link href="/produit/vedette" prefetch={true}>Produit vedette</Link>
Si 50 liens pointent vers des pages partageant le même layout, Next.js 16 télécharge le layout une seule fois (contre 50 fois avant). Pour une page avec une grille de produits, le gain est énorme : navigation quasi-instantanée et bande passante réduite de 90%.
Catch-all routes avec […slug]
app/docs/[...slug]/page.tsx
Capture :
/docs/intro → slug = ['intro']
/docs/api/auth → slug = ['api', 'auth']
/docs/api/v2/tokens → slug = ['api', 'v2', 'tokens']
Exercice pratique
- Créez
app/blog/[slug]/page.tsxqui affiche un article fictif selon le slug reçu - Ajoutez
generateStaticParamsavec 3 slugs en dur ('nextjs-16','react','typescript') - Ajoutez
generateMetadatapour avoir un<title>dynamique par article - Créez une liste dans
app/blog/page.tsxavec des<Link>vers ces 3 articles - Vérifiez dans l’onglet Network des DevTools que le prefetch se déclenche au survol