Optimisation des performances React Server Components : récupération de données et cache en pratique

Si le TTFB de vos pages RSC reste entre 300 et 500 ms, vous n’exploitez probablement qu’environ 30 % du potentiel de performance. Les mesures montrent qu’une architecture de streaming bien conçue peut faire passer le TTFB à 45 ms — ce n’est pas de la magie, c’est le rendu en streaming des React Server Components une fois réellement activé.
L’année dernière, en aidant une équipe e-commerce à optimiser une fiche produit, j’ai moi aussi marché dans ce piège. Ils utilisaient le Next.js App Router, mais le TTFB restait stable autour de 380 ms. Après investigation : des composants imbriqués récupéraient chacun leurs données, formant un « waterfall » typique : les infos produit attendaient les avis, les avis attendaient le prix, le prix attendait la vérification de stock. Neuf secondes d’écran blanc.
Cet article explique comment résoudre ce problème. Je compare 4 approches pour traiter le waterfall, détaille 5 API de cache et leurs cas d’usage, et vous donne des modèles de configuration prêts à copier. Du TTFB 450 ms au TTFB 45 ms, la différence peut tenir à quelques emplacements de limites Suspense.
Le problème de waterfall : le pire ennemi des performances RSC
Prenons un scénario concret. Vous ouvrez une fiche produit sur un site e-commerce : d’abord le nom s’affiche, trois secondes plus tard le prix, cinq secondes plus tard les avis. L’expérience utilisateur ? Catastrophique.
C’est le problème de waterfall. Des composants imbriqués récupèrent chacun leurs données, en séquence et non en parallèle. La récupération de données dans React Server Components est bloquante par défaut — toute requête avec await bloque le rendu, sauf si vous enveloppez avec Suspense.
Deux formes de waterfall
Première forme : waterfall interne au serveur. Sur une même page, le composant parent récupère les données avant de rendre les enfants, qui récupèrent ensuite les leurs. Code typique :
// Exemple de waterfall — code problématique
async function ProductPage({ id: string }) {
// Première requête : 1 seconde
const product = await db.getProduct(id);
// Ces requêtes ne démarrent qu'après le rendu des enfants
return (
<div>
<ProductDetails product={product} />
<ProductPrice id={id} /> {/* await getPrice(id) en interne, 3 s */}
<ProductReviews id={id} /> {/* await getReviews(id) en interne, 5 s */}
</div>
);
}
// ProductPrice.tsx
async function ProductPrice({ id }) {
const price = await getPrice(id); // S'exécute après le rendu du parent
return <span>{price}</span>;
}
Durée totale ? 9 secondes. L’utilisateur fixe un écran vide pendant 9 secondes.
Deuxième forme : waterfall client-serveur. Un composant client appelle le serveur, qui interroge la base. Plus discret ; seul React DevTools Profiler le révèle clairement. Une variante du problème N+1.
Comment repérer un waterfall
Ouvrez React DevTools Profiler et enregistrez un chargement de page. Si la timeline montre une distribution en marches d’escalier — chaque requête attend la précédente — c’est un waterfall.
Méthode plus directe : panneau Network du navigateur, heure de départ des requêtes. Si les requêtes de données partent de façon éparpillée plutôt qu’en bloc, le diagnostic est quasi certain.
Beaucoup de développeurs pensent que RSC apporte automatiquement des gains. Ce n’est pas le cas. Selon le rapport SitePoint 2026, la plupart des équipes n’exploitent qu’environ 30 % du potentiel RSC — faute d’avoir traité le waterfall.
Quatre solutions comparées : du brutal à l’élégant
Quatre approches dominantes existent pour le waterfall. Du simple au complexe, du brutal à l’élégant.
Solution 1 : récupération parallèle avec Promise.all
L’idée la plus directe : lancer toutes les requêtes ensemble et attendre avec Promise.all.
// Solution 1 : récupération parallèle avec Promise.all
async function ProductPage({ id: string }) {
// Lancer toutes les requêtes en même temps
const [product, price, reviews] = await Promise.all([
getProduct(id), // 1 s
getPrice(id), // 3 s
getReviews(id), // 5 s
]);
return (
<div>
<ProductDetails product={product} />
<ProductPriceDisplay price={price} />
<ProductReviewsList reviews={reviews} />
</div>
);
}
Durée totale ? 5 secondes. C’est la requête la plus lente qui fixe le temps global.
Avantages : simple, peu de changements.
Inconvénients : l’utilisateur attend la requête la plus lente avant de voir quoi que ce soit. Et couplage des données : le parent doit connaître les besoins des enfants, ce qui va à l’encontre de l’indépendance des composants.
Solution 2 : isolation par limites Suspense
Envelopper les parties dépendantes des données dans Suspense pour afficher d’abord le contenu critique.
// Solution 2 : isolation par limites Suspense
async function ProductPage({ id: string }) {
const product = await getProduct(id); // Attendre d'abord les données critiques
return (
<div>
<ProductDetails product={product} /> {/* Visible après 1 s */}
{/* Parties non critiques enveloppées dans Suspense */}
<Suspense fallback={<PriceSkeleton />}>
<ProductPrice id={id} />
</Suspense>
<Suspense fallback={<ReviewsSkeleton />}>
<ProductReviews id={id} />
</Suspense>
</div>
);
}
Expérience utilisateur : infos produit à 1 s, prix à 3 s, avis à 5 s.
Avantages : contenu critique en priorité, meilleure perception.
Inconvénients : les requêtes partent encore en séquence. Celles de ProductPrice et ProductReviews ne démarrent qu’après le rendu du parent — pas un vrai parallélisme.
Solution 3 : passer des Promise en props
Le parent lance toutes les requêtes et passe les Promise en props ; les enfants font leur propre await.
// Solution 3 : mode de passage de Promise
async function ProductPage({ id: string }) {
// Lancer toutes les requêtes immédiatement, sans await
const productPromise = getProduct(id);
const pricePromise = getPrice(id);
const reviewsPromise = getReviews(id);
// N'attendre que les données critiques
const product = await productPromise;
return (
<div>
<ProductDetails product={product} />
<Suspense fallback={<PriceSkeleton />}>
<ProductPrice pricePromise={pricePromise} />
</Suspense>
<Suspense fallback={<ReviewsSkeleton />}>
<ProductReviews reviewsPromise={reviewsPromise} />
</Suspense>
</div>
);
}
// ProductPrice.tsx — reçoit une Promise
async function ProductPrice({ pricePromise }) {
const price = await pricePromise; // Réutilise la Promise lancée par le parent
return <span>{price}</span>;
}
Les trois requêtes partent en parallèle dans le parent. Données critiques à 1 s, prix à 3 s, avis à 5 s.
Avantages : parallélisme complet, contenu critique en priorité, découplage (l’enfant ne reçoit qu’une Promise).
Inconvénients : modification de l’interface — l’enfant passe de id à Promise.
Solution 4 : React cache() + preload (recommandé)
React 19 introduit l’API cache(). Avec le pattern preload, c’est la solution la plus élégante.
// Solution 4 : React cache() + preload
import { cache } from 'react';
// Envelopper la fonction de récupération avec cache
const getComments = cache(async (postId: string) => {
return db.getComments(postId);
});
// Exporter une fonction preload pour clarifier l'usage
export const preloadComments = (id: string) => {
void getComments(id); // Pas d'await : lance sans bloquer
};
// Composant parent
async function PostPage({ postId: string }) {
preloadComments(postId); // Précharger les commentaires
const post = await getPost(postId); // N'attendre que les données critiques
return (
<div>
<PostContent post={post} />
<Suspense fallback={<CommentsSkeleton />}>
<Comments postId={postId} /> {/* id direct, cache réutilise automatiquement */}
</Suspense>
</div>
);
}
// Comments.tsx — interface inchangée
async function Comments({ postId }) {
const comments = await getComments(postId); // Réutilise la Promise du preload
return <CommentList comments={comments} />;
}
Principe : cache() mémorise automatiquement dans le même cycle de rendu. Le preload déclenche la requête sans attendre ; l’await enfant réutilise la même Promise.
Avantages :
- Interface des composants inchangée (on passe toujours
id) - Requêtes mémorisées automatiquement, sans couplage de données
- Si l’enfant est supprimé, le preload devient du code mort facile à repérer
Inconvénients : comprendre le mécanisme cache() ; attention au couplage caché (supprimer Comments implique de retirer le preload).
Comparaison des quatre solutions
| Solution | Durée totale | Contenu critique visible | Couplage données | Coût de changement |
|---|---|---|---|---|
| Récupération séquentielle | 9s | 9s | Non | Aucun |
| Promise.all | 5s | 5s | Oui | Faible |
| Suspense | 5s | 1s | Non | Faible |
| Passage de Promise | 5s | 1s | Découplé | Moyen |
| cache() + preload | 1s | 1s | Non | Moyen |
Le choix dépend de l’équipe. Migration rapide : solution 2 ; nouveau projet : solution 4.
Architecture de rendu en streaming : le secret du TTFB à 45 ms
Le SSR classique attend que toutes les données soient récupérées, rend le HTML complet, puis l’envoie d’un coup au navigateur. Le TTFB (Time to First Byte) égale temps de récupération plus temps de rendu.
Chiffres concrets : requête base 400 ms, rendu 50 ms, TTFB ≈ 450 ms. L’utilisateur attend près d’une demi-seconde devant un écran vide.
Comment le streaming RSC change ce flux
Le cœur du streaming n’est pas de « réduire » mais de changer l’ordre d’arrivée du contenu. Les parties statiques partent immédiatement, les parties dynamiques suivent en flux.
// Exemple d'architecture en streaming
export default async function Dashboard() {
return (
<Layout> {/* Shell statique, sans Suspense */}
<Nav /> {/* Rendu immédiat */}
<Sidebar /> {/* Rendu immédiat */}
<Suspense fallback={<ChartSkeleton />}>
<DynamicChart /> {/* Données dynamiques, chargement en flux */}
</Suspense>
<Suspense fallback={<TableSkeleton />}>
<DataTable /> {/* Données dynamiques, chargement en flux */}
</Suspense>
</Layout>
);
}
Déroulement :
- T=0 ms : shell statique (Layout, Nav, Sidebar) servi immédiatement depuis le cache edge CDN
- T=30-50 ms : le navigateur rend le shell statique, affiche les skeletons
- T=200 ms : données de DynamicChart prêtes, contenu de la limite Suspense envoyé en flux
- T=400 ms : données de DataTable prêtes, contenu correspondant envoyé en flux
TTFB ? Environ 45 ms — le moment d’envoi du shell statique.
Rôle du PPR (Partial Prerendering)
PPR est une fonctionnalité de Next.js 15 ; Next.js 16 l’activera par défaut. Il pré-rend les parties statiques sur le CDN et garde le streaming pour le dynamique.
Configuration :
// next.config.js — Next.js 15
module.exports = {
experimental: {
ppr: true, // Activer PPR
},
};
// next.config.js — Next.js 16 (aperçu)
module.exports = {
experimental: {
ppr: 'incremental', // Activation progressive
cacheComponents: true, // Nouveau modèle de cache
},
};
Avec PPR, le shell statique (navigation, layout, skeletons) est pré-rendu et stocké sur le CDN. À la visite, le CDN renvoie immédiatement le HTML statique ; le serveur complète en flux les parties dynamiques.
Principes de conception des limites Suspense
Principe clé : oublier Suspense sur les blocs streamés fait traiter toute l’application comme un seul bloc géant.
Bonne pratique :
- Parties statiques sans Suspense : navigation, Layout, skeletons sans dépendance de données
- Parties dynamiques avec Suspense : composants dépendant de la base ou d’API
// Bon exemple
export default async function Page() {
return (
<>
<Header /> {/* Statique, sans enveloppe */}
<main>
<Suspense fallback={<HeroSkeleton />}>
<HeroSection /> {/* Dynamique, enveloppé */}
</Suspense>
<Suspense fallback={<ContentSkeleton />}>
<MainContent /> {/* Dynamique, enveloppé */}
</Suspense>
</main>
<Footer /> {/* Statique, sans enveloppe */}
</>
);
}
Mauvais exemple (page entière bloquée) :
// Mauvais exemple — Suspense oublié
export default async function Page() {
const data = await fetchDashboard(); // await bloque toute la page
return (
<>
<Header />
<Dashboard data={data} />
<Footer />
</>
);
}
Sans limite Suspense, toute la page compte comme un seul bloc streamé. TTFB toujours à 450 ms.
Comparaison des données de performance
| Mode de rendu | TTFB | LCP | Description |
|---|---|---|---|
| SSR classique | ~450ms | ~500ms | Attente de toutes les données |
| RSC (sans Suspense) | ~450ms | ~500ms | Équivalent au SSR classique |
| RSC Streaming | ~45ms | ~200ms | Shell statique envoyé immédiatement |
| RSC + PPR | ~30ms | ~150ms | Shell statique en cache CDN |
Source : rapport SitePoint 2026 ; les mesures réelles peuvent varier selon la source de données et la config CDN.
Guide des cinq API de cache
Next.js et React proposent cinq mécanismes de cache. Le bon choix multiplie l’effet ; le mauvais peut dupliquer les requêtes.
1. fetch cache (le plus courant)
Les requêtes fetch dans les Server Components sont mémorisées automatiquement. Dans le même cycle de rendu, une même URL et les mêmes paramètres ne déclenchent qu’une requête.
// Exemple fetch cache
async function ProductCard({ id }) {
// Cache automatique, pas de requête dupliquée pour la même URL
const res = await fetch(`https://api.example.com/products/${id}`, {
cache: 'force-cache', // Cache forcé (défaut)
next: {
revalidate: 3600, // Revalidation après 1 heure
tags: ['products'], // Tag pour revalidateTag
},
});
return <Card data={res.json()} />;
}
async function ProductList() {
// Cette requête réutilise le cache ci-dessus
const res = await fetch('https://api.example.com/products', {
next: { tags: ['products'] },
});
return <List data={res.json()} />;
}
Options de configuration :
cache: 'force-cache': privilégier le cache (défaut)cache: 'no-store': requête à chaque foisnext.revalidate: revalidation programmée (secondes)next.tags: tags pour revalidateTag manuel
2. React cache() (nouveau en React 19)
Pour mémoriser le résultat d’appels de fonctions : requêtes base, fonctions de récupération personnalisées.
import { cache } from 'react';
// Envelopper une requête base avec cache
export const getUser = cache(async (id: string) => {
const user = await db.query('SELECT * FROM users WHERE id = ?', [id]);
return user;
});
// Utilisation dans plusieurs composants, mémorisation automatique
async function UserProfile({ id }) {
const user = await getUser(id);
return <Profile user={user} />;
}
async function UserStats({ id }) {
const user = await getUser(id); // Réutilise le résultat ci-dessus
return <Stats user={user} />;
}
Note : cache() ne vaut que pour un cycle de rendu. Pour persister entre requêtes, utiliser unstable_cache.
3. unstable_cache (Next.js 14-15)
Cache persistant entre requêtes. Pour calculs coûteux et données partagées entre pages.
import { unstable_cache } from 'next/cache';
// Envelopper une fonction avec cache persistant
export const getPopularProducts = unstable_cache(
async () => {
const products = await db.getPopularProducts();
return products;
},
['popular-products'], // Clé de cache
{
revalidate: 3600, // Revalidation après 1 heure
tags: ['products', 'popular'], // Tags multiples
}
);
// Utilisation
async function HomePage() {
const products = await getPopularProducts();
return <ProductGrid products={products} />;
}
Invalidation manuelle :
import { revalidateTag } from 'next/cache';
// Dans une Server Action ou une API Route
async function updateProduct() {
await db.updateProduct();
await revalidateTag('products'); // Invalide tout cache tagué products
}
4. use cache (nouveau en Next.js 16)
Directive de cache au niveau composant ou fonction. 'use cache' en tête, la sortie est mise en cache automatiquement.
// 'use cache' au niveau fonction
'use cache';
export async function getRecommendations(userId: string) {
return db.getRecommendations(userId);
}
// 'use cache' au niveau composant
'use cache';
export async function CachedFooter() {
const links = await getFooterLinks();
return <Footer links={links} />;
}
Cas d’usage : composants très sollicités, contenu statique. Fonctionnalité expérimentale, support officiel en Next.js 16.
5. revalidatePath / revalidateTag
Méthodes d’invalidation manuelle du cache.
import { revalidatePath, revalidateTag } from 'next/cache';
// Invalidation par chemin
await revalidatePath('/products'); // Invalide tout le cache de ce chemin
await revalidatePath('/products/[id]', 'page'); // Invalide une page précise
// Invalidation par tag
await revalidateTag('products'); // Invalide tout cache tagué products
Recommandations :
- Contrôle fin :
revalidateTag(recommandé) - Invalidation en masse :
revalidatePath
Comparaison des API de cache
| API | Portée | Persistance | Cas d’usage | Version |
|---|---|---|---|---|
| fetch cache | Requête unique | Configurable | Requêtes API | Next.js 13+ |
| React cache() | Cycle de rendu | Non | Requêtes base, fonctions custom | React 19 |
| unstable_cache | Entre requêtes | Oui | Calculs coûteux, données partagées | Next.js 14-15 |
| use cache | Fonction/composant | Oui | Composants fréquents | Next.js 16 |
| Cache Components | Sortie composant | Oui | Avec PPR | Next.js 16 |
Modèles de configuration et pièges courants
Assez de théorie — voici des configurations copiables.
Configuration complète next.config.js
// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
// Next.js 15 : activer PPR
ppr: true,
// Next.js 16 : nouveau modèle de cache
// cacheComponents: true, // Activer en version stable
},
// Performance
images: {
formats: ['image/avif', 'image/webp'],
},
// Optimisation de sortie
output: 'standalone', // Pour déploiement Docker
};
export default nextConfig;
Convention d’export des fonctions preload
Les fonctions preload créent facilement un couplage caché. En supprimant un composant enfant profond, le preload peut devenir du code mort.
Bonne pratique : commenter au-dessus du preload pour indiquer son rôle.
// comments.ts
import { cache } from 'react';
const getComments = cache(async (postId: string) => {
return db.getComments(postId);
});
/**
* preloadComments : précharge les commentaires pour le composant Comments
* Attention : supprimer Comments implique de retirer aussi cette fonction preload
*/
export const preloadComments = (id: string) => {
void getComments(id);
};
export async function Comments({ postId }) {
const comments = await getComments(postId);
return <CommentList comments={comments} />;
}
Pièges fréquents
Piège 1 : oublier les limites Suspense
Symptôme : TTFB toujours à 450 ms, pas d’effet streaming.
Cause : sans Suspense, React traite toute la page comme un seul bloc.
Correction : envelopper les composants dépendants des données.
// Avant correction
async function Page() {
const data = await getData(); // Bloque toute la page
return <Dashboard data={data} />;
}
// Après correction
async function Page() {
return (
<Suspense fallback={<DashboardSkeleton />}>
<Dashboard />
</Suspense>
);
}
Piège 2 : preload sans utilisation
Symptôme : requête lancée mais données inutilisées — gaspillage.
Cause : composant enfant supprimé mais preload conservé.
Correction : supprimer le preload avec le composant, ou commenter le lien.
Piège 3 : conflit de tags de cache
Symptôme : revalidateTag invalide trop large, des données non concernées aussi.
Cause : caches sans rapport partagent le même tag.
Correction : tags distincts par domaine métier.
// Mauvais exemple
await fetch(url, { next: { tags: ['data'] } }); // Tout tagué 'data'
await revalidateTag('data'); // Invalide tout
// Bon exemple
await fetch(productsUrl, { next: { tags: ['products'] } });
await fetch(usersUrl, { next: { tags: ['users'] } });
await revalidateTag('products'); // Invalide seulement products
Piège 4 : mélanger fetch cache et React cache
Symptôme : deux requêtes pour les mêmes données.
Cause : fetch avec 'no-store', React cache() ne peut pas réutiliser.
Correction : fetch en force-cache ou défaut pour permettre la réutilisation.
// Mauvais exemple
const data1 = await fetch(url, { cache: 'no-store' }); // Pas de cache
const data2 = await getData(); // React cache, mais pas de réutilisation
// Bon exemple
const data1 = await fetch(url); // force-cache par défaut
const data2 = await getData(); // Peut réutiliser
Outils de débogage
- React DevTools Profiler : enregistrer le rendu, repérer la distribution waterfall
- Outils d’analyse Next.js :
next build --experimental-debugpour l’analyse de build - Chrome DevTools : panneau Network pour la chronologie des requêtes, Performance pour le timing de rendu
Indicateurs clés :
- TTFB : time to first byte, cible < 100 ms
- LCP : largest contentful paint, cible < 2,5 s
- CLS : cumulative layout shift, cible < 0,1
Conseils de migration
Migrer des pages SSR existantes vers le streaming RSC :
- Repérer le waterfall : Profiler, identifier la récupération séquentielle
- Ajouter des limites Suspense : envelopper les composants dépendants des données, prioriser le chemin critique
- Ajouter preload :
cache()+ preload pour les composants profonds - Configurer le cache : tags sur
fetchpour invalidation précise
Migrer progressivement, pas de refonte d’un coup. Commencer par les pages les plus lentes, mesurer, puis généraliser.
Conclusion
En résumé, trois étapes : repérer le waterfall, choisir une solution, configurer le streaming.
Le waterfall se repère facilement — composants imbriqués qui await chacun leurs données, timeline en marches d’escalier. Quatre solutions selon le contexte : migration rapide avec Suspense, nouveau projet avec React cache() + preload.
La clé du streaming, ce sont les emplacements des limites Suspense. Parties statiques (nav, Layout) sans enveloppe ; parties dynamiques (dépendantes des données) obligatoirement enveloppées. Sans ça, toute la page reste bloquante.
Le gain se mesure : TTFB de 450 ms à 45 ms, un facteur 10. Il peut suffire de quelques limites Suspense et d’une fonction preload.
Ouvrez votre projet Next.js et cherchez des composants imbriqués qui récupèrent chacun leurs données. Si le TTFB dépasse encore 300 ms, essayez d’envelopper le contenu critique dans Suspense. Vos utilisateurs sentiront la différence tout de suite.
Références
- Data Fetching Patterns and Best Practices — Documentation officielle Next.js
- React Server Components Streaming Performance Guide 2026 — SitePoint, 2026-02
- Avoiding Server Component Waterfall Fetching with React 19 cache() — Aurora Scharff, 2025-02
- Avoiding Waterfalls in React Server Components — Akhila Ariyachandra, 2026-01
- Functions: revalidatePath — Documentation officielle Next.js
- Functions: revalidateTag — Documentation officielle Next.js
- Partial Prerendering (PPR) — Documentation officielle Next.js
- React v19 Release — Blog officiel React
Processus d'optimisation des performances React Server Components
Étapes complètes d'optimisation, de l'identification du waterfall à la configuration du streaming
⏱️ Estimated time: 60 min
- 1
Step 1: Identifier le problème de waterfall
Enregistrer le chargement avec React DevTools Profiler :
• Ouvrir Chrome DevTools, onglet Profiler
• Enregistrer, rafraîchir la page, attendre la fin du chargement
• Examiner la distribution des requêtes sur la timeline
• Distribution en marches d'escalier = waterfall
• Vérifier si le TTFB dépasse 300 ms - 2
Step 2: Choisir une solution
Selon le contexte de l'équipe :
• Migration rapide : solution 2 (isolation Suspense)
• Nouveau projet : solution 4 (React cache() + preload)
• Couplage de données acceptable : solution 1 (Promise.all)
• Interface composant inchangée : solution 4 (recommandé) - 3
Step 3: Ajouter des limites Suspense
Envelopper les composants dépendants des données :
• Parties statiques sans enveloppe (nav, Layout)
• Parties dynamiques obligatoirement enveloppées
• Fournir un skeleton fallback adapté
• Traiter d'abord le chemin critique - 4
Step 4: Configurer React cache() + preload
Utiliser l'API cache() de React 19 :
• Envelopper les fonctions de récupération avec cache
• Exporter une fonction preload sans await
• Commenter le lien composant/preload
• Supprimer le preload en même temps que le composant - 5
Step 5: Configurer la stratégie de cache
Choisir l'API de cache adaptée :
• fetch cache : requêtes API (le plus courant)
• React cache() : requêtes base de données
• unstable_cache : partage entre requêtes
• Ajouter des tags pour invalidation précise - 6
Step 6: Mesurer les gains de performance
Valider l'optimisation :
• Cible TTFB : < 100 ms
• Cible LCP : < 2,5 s
• Cible CLS : < 0,1
• Comparer les métriques avant/après optimisation
FAQ
Qu'est-ce que le problème de waterfall avec React Server Components ?
Comment choisir entre les quatre solutions au waterfall ?
• Promise.all : simple et direct, couplage de données possible
• Limites Suspense : contenu critique en priorité, adapté à une migration rapide
• Passage de Promise : parallélisme complet si l'interface peut changer
• React cache() + preload : solution la plus élégante pour un nouveau projet (recommandé)
Où placer les limites Suspense ?
Quelle différence entre les cinq API de cache ?
• fetch cache : requêtes API, mémorisation automatique (Next.js 13+)
• React cache() : requêtes base, cache sur un cycle de rendu (React 19)
• unstable_cache : persistance entre requêtes, calculs coûteux (Next.js 14-15)
• use cache : cache fonction/composant (Next.js 16)
• revalidatePath/Tag : invalidation manuelle
Comment mesurer l'effet de l'optimisation RSC ?
Qu'est-ce que le PPR (Partial Prerendering) ?
Quels pièges courants en configuration de cache ?
• Limites Suspense oubliées : page bloquée, TTFB inchangé
• preload inutilisé : composant supprimé mais preload conservé
• Conflit de tags : revalidateTag trop large
• Mélange fetch cache et React cache : no-store empêche la réutilisation
15 min de lecture · Publié le: 13 mai 2026 · Mis à jour le: 27 juil. 2026
Guide complet Next.js
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Next.js App Router + shadcn/ui : guide pour mélanger Server et Client Components
Guide pratique pour bien combiner Server Components et Client Components dans Next.js App Router : intégration shadcn/ui, conception du flux de données, correction des erreurs courantes et optimisation des performances
Partie 46 sur 51
Suivant
NextAuth.js : tutoriel d'introduction — Credentials, sessions JWT vs base de données
NextAuth.js vous semble complexe ? Ce guide se concentre sur Credentials, le choix JWT vs Session, avec exemples de code complets et pièges courants pour les développeurs Next.js.
Partie 48 sur 51



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire