Next.js 15 avait déjà amorcé un virage important avec le passage aux APIs async et la fondation React 19. Next.js 16 termine ce que la version 15 avait commencé. Turbopack n’est plus opt-in. Le modèle de cache est enfin prévisible. Le Partial Prerendering mélange rendu statique et dynamique au niveau du composant. Et le framework s’appuie encore plus sur les primitives de React 19, faisant des Server Functions et du hook use() des citoyens de première classe de l’expérience de développement.
Turbopack : le bundler par défaut
Turbopack est en bêta depuis Next.js 13. Avec la version 16, il remplace webpack comme bundler par défaut pour le développement et la production. La différence n’est pas subtile.
# Cold start sur un gros projet
webpack: 12.4s
turbopack: 2.1s
Les mises à jour HMR qui prenaient 800ms avec webpack atterrissent en moins de 50ms. L’amélioration grandit avec la taille du projet parce que Turbopack ne recompile que les modules modifiés, pas l’ensemble du graphe de dépendances. Pour les grosses codebases avec des centaines de routes, le dev server passe d’un goulot d’étranglement à quelque chose qu’on oublie qu’il tourne.
La migration est transparente. Turbopack lit le next.config.ts de la même façon que webpack. Les configurations webpack custom qui reposent sur des loaders ou plugins nécessitent une migration, mais les projets Next.js standards fonctionnent sans aucun changement.
Le nouveau modèle de cache
Next.js 15 avait commencé à défaire les defaults de cache agressifs qui embrouillaient les développeurs. Next.js 16 achève la transition avec un modèle qui a enfin du sens.
// Dynamique par défaut : pas de cache sauf opt-in explicite
async function ProductsPage() {
const products = await fetch('https://api.example.com/products');
return <ProductList products={await products.json()} />;
}
// Opt-in explicite pour le cache
async function ProductsPage() {
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 },
});
return <ProductList products={await products.json()} />;
}
Les requêtes fetch ne sont plus cachées par défaut. Les route handlers GET ne sont plus cachés par défaut. Les navigations côté client ne servent plus un prefetch cache périmé pour les pages dynamiques. Chaque comportement de cache est maintenant opt-in et explicite.
Ça ressemble à une régression de performance, mais c’est l’inverse. Les anciens defaults menaient à des bugs de données périmées incroyablement difficiles à débugger. Des équipes passaient des heures à se demander pourquoi leur dashboard affichait les chiffres de la veille. Le nouveau modèle est prévisible : si on veut du cache, on le dit. Sinon, les données sont toujours fraîches.
Partial Prerendering : statique et dynamique dans une seule page
Le Partial Prerendering (PPR) est la feature qui rend Next.js 16 architecturalement différent de tout le reste dans l’écosystème React. Une seule page peut avoir un shell statique qui arrive instantanément et des zones dynamiques qui streament depuis le serveur.
import { Suspense } from 'react';
export default function Dashboard() {
return (
<div>
{/* Statique : rendu au build */}
<h1>Dashboard</h1>
<Navigation />
{/* Dynamique : streamé à la requête */}
<Suspense fallback={<StatsSkeleton />}>
<LiveStats />
</Suspense>
<Suspense fallback={<FeedSkeleton />}>
<ActivityFeed />
</Suspense>
</div>
);
}
Les parties statiques (h1, Navigation) sont prérendues dans un shell HTML servi depuis le CDN edge. Les parties dynamiques (LiveStats, ActivityFeed) sont calculées à la requête et streamées dans le shell. L’utilisateur voit la structure de la page instantanément, et les données se remplissent progressivement.
Ce n’est pas la même chose que du SSG + fetching côté client. Les parties dynamiques tournent sur le serveur. Elles ont accès à la base de données, aux secrets, aux headers de requête. Le navigateur reçoit du HTML rendu, pas du JavaScript qui doit fetcher des données après l’hydratation.
APIs de requête asynchrones
Les headers, cookies, params et search params sont maintenant entièrement asynchrones. Ce changement a démarré dans Next.js 15 comme une dépréciation et est désormais appliqué.
// Avant (Next.js 14) : accès synchrone
export default function Page({ params }: { params: { id: string } }) {
const id = params.id;
return <div>{id}</div>;
}
// Après (Next.js 16) : accès asynchrone
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
return <div>{id}</div>;
}
Même chose pour headers(), cookies() et searchParams. Ils retournent tous des promises maintenant. Cela permet au runtime de différer l’évaluation et d’optimiser l’ordre de rendu. Ça signifie aussi que ces valeurs fonctionnent naturellement avec les boundaries Suspense et le streaming.
L’API after() : du travail post-réponse
after() permet de planifier du travail qui s’exécute après que la réponse a été envoyée au client. Analytics, logging, réchauffement de cache, tout ce qui ne devrait pas retarder la réponse utilisateur.
import { after } from 'next/server';
export async function POST(request: Request) {
const order = await processOrder(request);
after(async () => {
await analytics.track('purchase', { orderId: order.id });
await emails.sendConfirmation(order);
await cache.invalidate(['user-orders', order.userId]);
});
return Response.json({ success: true, orderId: order.id });
}
La réponse part immédiatement après processOrder. Le tracking analytics, l’envoi d’email et l’invalidation de cache se font en arrière-plan. Pas de queue externe, pas de worker séparé, pas d’infrastructure ajoutée. Ce pattern remplace une part significative de ce pour quoi les équipes montaient des services séparés.
React 19 comme fondation
Next.js 16 adopte pleinement React 19 comme runtime. Le React Compiler est intégré dans le pipeline de build. Les Server Functions (l’évolution des Server Actions) fonctionnent sans la directive "use server" en haut de fichiers dédiés, elles peuvent être définies inline.
export default async function PostsPage() {
const posts = await db.post.findMany();
async function deletePost(id: string) {
'use server';
await db.post.delete({ where: { id } });
revalidatePath('/posts');
}
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
{post.title}
<DeleteButton onDelete={() => deletePost(post.id)} />
</li>
))}
</ul>
);
}
La directive 'use server' marque des fonctions individuelles, pas des fichiers. La fonction se ferme sur des variables côté serveur (db, revalidatePath) et est sérialisée comme une référence pour le client. Combiné avec le React Compiler qui élimine la mémoïsation manuelle, les composants sont plus propres et plus courts que jamais.
Conclusion
Next.js 16 est la release où tout s’emboîte. Turbopack rend l’expérience dev assez rapide pour oublier la toolchain. Le modèle de cache arrête de se battre contre les développeurs et commence à travailler avec eux. Le Partial Prerendering tient la promesse de chargements de page instantanés sans sacrifier les données dynamiques. Pour les équipes déjà sur l’App Router, cette mise à jour est directe et le gain est immédiat.