Next.js10 min de lecture
Données et cache
Depuis Next.js 16, tout est dynamique par défaut et le cache est explicite. Tu décides, composant par composant, ce qui peut être mis en cache et ce qui doit être frais à chaque requête.
Le modèle Cache Components
Un composant marqué 'use cache' est rendu à l'avance et fait partie de la coquille statique. Un composant qui lit des données de requête (cookies(), headers()) reste dynamique et arrive en flux dans un Suspense. Change les interrupteurs, puis simule une navigation.
- Catégories
'use cache' cacheLife('days')Change rarement : une journée de cache.
- Liste de produits
'use cache' cacheTag('products')Taguée : invalidée dès qu'un produit est modifié.
- Panier
await cookies() <Suspense>
Lit le cookie de session : toujours dynamique, sous Suspense.
Où charger les données
| Besoin | Outil |
|---|---|
| Afficher des données dans une page | Server Component async qui appelle une fonction de queries.ts |
| Créer, modifier, supprimer | Server Action dans actions.ts, validée par Zod |
| Webhook, endpoint appelé par un tiers | Route Handler app/api/…/route.ts |
| Temps réel, polling, défilement infini | TanStack Query dans un Client Component, rarement |
Pas de cascade de requêtes
Quand plusieurs données sont indépendantes, lance-les en parallèle avec Promise.all. Si une seule est lente, isole-la dans son propre Suspense.
À éviter
// Waterfall: 3 sequential requests (300 + 250 + 400 ms)
const customer = await getCustomer(id);
const invoices = await getInvoices(id);
const stats = await getStats(id);À faire
// In parallel: as slow as the slowest request (400 ms)
const [customer, invoices, stats] = await Promise.all([
getCustomer(id),
getInvoices(id),
getStats(id),
]);Mettre en cache
'use cache' s'applique à une fonction ou un composant. cacheLife fixe la durée ('hours', 'days', 'max'), cacheTag donne un nom pour invalider plus tard.
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
cacheComponents: true, // explicit caching with 'use cache'
partialPrefetching: true, // Instant Navigations (16.3)
};
export default nextConfig;import { cacheLife, cacheTag } from 'next/cache';
import { apiFetch } from '@/lib/api-client';
export async function getCategories() {
'use cache';
cacheLife('days');
return apiFetch('/v1/categories');
}
export async function getProducts(categoryId: number) {
'use cache';
cacheLife('hours');
cacheTag('products', `category-${categoryId}`);
return apiFetch(`/v1/categories/${categoryId}/products`);
}import { cookies } from 'next/headers';
import { Suspense } from 'react';
export default async function ShopPage({ params }: PageProps<'/[locale]/shop/[category]'>) {
const { category } = await params;
return (
<>
<CategoryNav /> {/* cached: in the static shell */}
<ProductGrid categoryId={Number(category)} /> {/* cached with a tag */}
<Suspense fallback={<CartSkeleton />}>
<Cart /> {/* dynamic: reads cookies */}
</Suspense>
</>
);
}
async function Cart() {
const session = (await cookies()).get('session')?.value; // request-time data
const cart = await getCart(session);
return <CartSummary cart={cart} />;
}Après une modification
Une mutation doit dire à Next.js quelles données ne sont plus à jour. Choisis l'API selon ce que l'utilisateur attend.
| API | Quand |
|---|---|
updateTag(tag) | Dans une Server Action : l'utilisateur doit voir sa modification immédiatement. |
revalidateTag(tag, 'max') | Contenu qui peut être légèrement en retard : l'ancien est servi pendant le recalcul. |
refresh() | Rafraîchir uniquement les données non mises en cache (compteur de notifications). |
router.refresh() | Côté client, recharger les Server Components de la route courante. |
'use server';
import { refresh, revalidateTag, updateTag } from 'next/cache';
export async function renameProduct(id: number, name: string) {
await apiFetch(`/v1/products/${id}`, { method: 'PATCH', body: { name } });
updateTag('products'); // the user sees their change right away
}
export async function importCatalog() {
await apiFetch('/v1/catalog/import', { method: 'POST' });
revalidateTag('products', 'max'); // stale-while-revalidate for everyone else
}
export async function markNotificationRead(id: number) {
await apiFetch(`/v1/notifications/${id}/read`, { method: 'POST' });
refresh(); // re-render uncached data only (header badge)
}proxy.ts
Next.js 16 renomme middleware.ts en proxy.ts. On y fait la détection de langue et une vérification optimiste de session. L'autorisation réelle reste dans Laravel.
import { NextResponse, type NextRequest } from 'next/server';
import createIntlMiddleware from 'next-intl/middleware';
import { routing } from './i18n/routing';
const intl = createIntlMiddleware(routing);
const protectedPaths = /^\/(fr|en)\/(dashboard|invoices)/;
export default function proxy(request: NextRequest) {
// Optimistic check only: real authorization happens in Laravel
const hasSession = request.cookies.has('laravel_session');
if (protectedPaths.test(request.nextUrl.pathname) && !hasSession) {
return NextResponse.redirect(new URL('/fr/login', request.url));
}
return intl(request);
}
export const config = { matcher: '/((?!api|_next|_vercel|.*\\..*).*)' };Lire la langue n'importe où
Nouveau dans Next.js 16.3 : next/root-params donne accès au paramètre [locale] depuis n'importe quel Server Component, même dans un 'use cache'.
import { locale } from 'next/root-params'; // Next.js 16.3
// Any Server Component, however deep, without prop drilling
export async function PriceTag({ amount }: { amount: number }) {
const current = await locale();
return <span>{new Intl.NumberFormat(current, { style: 'currency', currency: 'EUR' }).format(amount)}</span>;
}