Aller au contenu principal
Développement Web16 min de lecture

Partial Pre-Rendering (PPR) dans Next.js : Le Guide Complet

Le PPR combine le meilleur du statique et du dynamique. Avec la directive use cache, Next.js révolutionne le rendu web. Guide technique avec exemples.

Partial Pre-Rendering (PPR) dans Next.js : Le Guide Complet

Partial Pre-Rendering (PPR) dans Next.js : Le Guide Complet

Et si vous n'aviez plus à choisir entre une page rapide et une page à jour ? C'est exactement la promesse du PPR.

Le Partial Pre-Rendering (PPR) de Next.js est sans doute la fonctionnalité de rendu la plus structurante depuis l'arrivée des Server Components. Pendant des années, construire une page web revenait à trancher un dilemme : statique (rapide mais figé) ou dynamique (à jour mais lent). Le Partial Pre-Rendering Next.js efface cette frontière en permettant de servir, sur une seule et même page, un squelette statique instantané et des zones dynamiques streamées à la volée.

Dans ce guide complet, vous allez comprendre précisément ce qu'est le PPR, quel problème il résout face au SSR, au SSG et à l'ISR, comment il fonctionne techniquement, comment l'activer prudemment selon la version de Next.js, et surtout quels pièges éviter en production. Les exemples de code sont volontairement simplifiés pour aller à l'essentiel.


Qu'est-ce que le Partial Pre-Rendering dans Next.js ?

Le Partial Pre-Rendering est un modèle de rendu hybride. L'idée tient en une phrase : pré-générer au build tout ce qui peut l'être, et ne streamer à la requête que ce qui est réellement dynamique.

Concrètement, Next.js construit au moment du build un shell statique — l'ossature de la page, identique pour tous les visiteurs (header, mise en page, contenus stables). Ce shell est mis en cache et peut être servi depuis un CDN avec une latence minimale. À l'intérieur de ce shell, certaines zones sont marquées comme dynamiques : ce sont des « trous » que Next.js remplit ensuite par streaming, au moment de la requête.

Le résultat perçu par l'utilisateur : la page apparaît immédiatement (le shell), puis les parties personnalisées ou temps réel se remplissent progressivement, sans recharger la page. On obtient la vitesse du statique et la fraîcheur du dynamique, sans avoir à classer la page entière dans une seule catégorie.

C'est un changement de granularité fondamental. Avant, la page était l'unité de décision : statique ou dynamique. Avec le PPR, l'unité devient le composant. Chaque morceau de l'arbre React peut adopter le mode le plus pertinent.


Quel problème de performance le Partial Pre-Rendering résout-il ?

Pour saisir l'intérêt du PPR, prenons une page e-commerce typique. Elle mélange des éléments dont la nature est radicalement différente :

ÉlémentNatureBesoin
Header / Logo / NavigationStatiqueIdentique pour tous
Fiche produit (titre, description)Semi-dynamiqueChange rarement
Prix, stockDynamiqueTemps réel
Panier, recommandationsTrès dynamiqueUnique par utilisateur

Avant le PPR, vous deviez choisir une stratégie pour la page entière :

  • SSG (Static Site Generation) : la page entière est générée au build. Ultra-rapide, mais le prix et le panier sont figés au moment du build — inacceptable pour de l'e-commerce.
  • SSR (Server-Side Rendering) : la page entière est rendue à chaque requête. Toujours à jour, mais le visiteur attend que tout soit calculé côté serveur avant de voir quoi que ce soit — y compris le header qui n'a pourtant jamais changé.
  • ISR (Incremental Static Regeneration) : un compromis qui régénère la page en arrière-plan à intervalles réguliers. Mieux, mais toujours pensé au niveau de la page, et inadapté aux contenus strictement temps réel ou personnalisés.

Le problème de fond est qu'aucune de ces stratégies n'est juste pour toute la page. Servir le header en SSR est un gâchis ; figer le prix en SSG est dangereux. Le PPR résout ce conflit en appliquant la bonne stratégie à chaque zone. Le header part dans le shell statique ; le prix devient un trou dynamique streamé. Plus besoin de sacrifier la vitesse globale à cause d'une seule donnée volatile.

Si vous voulez aller plus loin sur les causes de lenteur structurelle d'une application, notre article pourquoi votre app est lente : le problème du state management complète bien cette réflexion côté front.


Comment fonctionne techniquement le PPR ?

Le mécanisme repose sur deux phases distinctes : le build et la requête.

Architecture du PPR — shell statique pré-rendu au build, trous dynamiques streamés à la requêteAu build, Next.js pré-rend le shell statique et y intègre les fallbacks Suspense. À la requête, ce shell est servi instantanément depuis le CDN, puis les trous dynamiques sont streamés depuis le serveur.

Comment Next.js construit-il le shell statique ?

Au build, Next.js parcourt l'arbre de composants et pré-rend tout ce qui ne dépend pas de la requête : le layout, les composants stables, et les contenus explicitement mis en cache. Tout ce qui se trouve à l'intérieur d'une frontière <Suspense> non résolue au build est remplacé par son fallback (skeleton, spinner). Ce fallback fait partie du shell statique : c'est pourquoi l'utilisateur voit immédiatement une page structurée, même avant que les données dynamiques n'arrivent.

Comment les trous dynamiques sont-ils streamés ?

À la requête, le shell est renvoyé en premier, sans attendre. En parallèle, le serveur calcule le contenu des zones dynamiques et le streame dans la même réponse HTTP. React remplace alors progressivement chaque fallback par le contenu réel, au fur et à mesure de son arrivée. Cette séquence — réponse statique immédiate, puis hydratation progressive des parties dynamiques — est ce qui donne au PPR son ressenti de fluidité.

Séquence de rendu PPR — shell statique servi puis hydratation dynamique progressiveAvec PPR, le shell statique est servi instantanément depuis le CDN, puis les parties dynamiques (prix, panier) sont streamées en parallèle depuis le serveur et hydratées progressivement.

Suspense ou use cache : quelle est la vraie différence ?

C'est le point le plus souvent mal compris. Selon la documentation Next.js récente, ces deux outils ne font pas la même chose :

  • <Suspense> gère le timing de livraison. Il dit à Next.js : « envoie ce morceau plus tard, après le shell. » Il crée un trou dynamique, mais ne met rien en cache.
  • use cache gère le calcul. Il dit à Next.js : « ne recalcule pas ce résultat, réutilise la valeur mémorisée. » Il évite la réexécution du code.

Un point crucial souvent ignoré : le shell statique est une optimisation de livraison, pas de calcul. Sans use cache, le serveur réexécute quand même l'arbre complet à chaque requête pour résoudre les références de composants — il livre simplement le shell plus tôt. Pour réellement éviter le recalcul des parties stables, il faut leur appliquer use cache. En pratique, on combine donc les deux : Suspense pour la livraison instantanée, use cache pour ne pas refaire le travail.


Comment activer le Partial Pre-Rendering dans Next.js ?

Le statut du PPR a évolué, et c'est essentiel à comprendre pour ne pas suivre un tutoriel obsolète.

L'approche historique : experimental.ppr (Next.js 15)

Dans Next.js 15, le PPR était une fonctionnalité expérimentale, activée explicitement via un drapeau de configuration :

// next.config.ts — approche Next.js 15 (exemple simplifié)
const nextConfig = {
  experimental: {
    ppr: true, // ou 'incremental' pour une adoption page par page
  },
};

export default nextConfig;

La valeur 'incremental' permettait une adoption progressive, page par page, en optant explicitement pour le PPR sur chaque route concernée — une approche prudente, idéale pour migrer un site existant sans tout casser d'un coup.

L'approche moderne : cacheComponents (Next.js récent)

Selon la documentation Next.js récente, le PPR a été consolidé dans une primitive plus large appelée Cache Components. Le drapeau dédié experimental.ppr est désormais superseded (conservé pour la compatibilité ascendante), et le PPR devient le comportement de rendu par défaut lorsque l'on active Cache Components :

// next.config.ts — approche récente (exemple simplifié)
const nextConfig = {
  cacheComponents: true,
};

export default nextConfig;

Avec cette configuration, vous n'allumez plus de drapeau « PPR » séparé : les routes apparaissent simplement comme des partial prerenders dans la sortie de next build (matérialisées par un symbole de demi-cercle). C'est la même idée mentale qu'avant, mais intégrée à un système de cache unifié. Pour un panorama des nouveautés de cette génération de Next.js, notre article Next.js 16.2 : ce qui change concrètement détaille le contexte plus large.

La directive use cache et la revalidation

Une fois Cache Components actif, vous contrôlez le rendu zone par zone avec use cache :

// Exemple simplifié — composant mis en cache
import { cacheLife } from 'next/cache';

async function ProductInfo({ id }: { id: string }) {
  'use cache';
  cacheLife('hours'); // durée de validité du cache
  const product = await db.product.findUnique({ where: { id } });
  return (
    <div>
      <h2>{product.name}</h2>
      <p>{product.description}</p>
    </div>
  );
}

La fonction cacheLife définit la durée de validité (heures, jours, etc.), tandis que des mécanismes de revalidation par tag permettent d'expirer le cache à la demande — par exemple lorsqu'un produit est mis à jour côté back-office. Toute la finesse du PPR tient dans ce dosage entre ce qui est mis en cache, ce qui est revalidé, et ce qui reste strictement dynamique.


Exemples de code : Suspense et composants dynamiques

Reprenons notre page produit et voyons comment les pièces s'assemblent. L'exemple ci-dessous est simplifié mais illustre le pattern clé : un composant stable mis en cache, un composant temps réel laissé dynamique, et des frontières Suspense pour délimiter les trous.

// Exemple simplifié — page produit avec PPR
import { Suspense } from 'react';

// Mis en cache : la fiche produit change rarement
async function ProductInfo({ id }: { id: string }) {
  'use cache';
  const product = await db.product.findUnique({ where: { id } });
  return <div>{product.name} — {product.description}</div>;
}

// Dynamique : le prix doit refléter le temps réel
async function LivePrice({ id }: { id: string }) {
  const price = await fetchLivePrice(id);
  return <span>{price} EUR</span>;
}

export default async function ProductPage({ params }) {
  return (
    <div>
      {/* Le header (hors Suspense) part dans le shell statique */}
      <Suspense fallback={<ProductSkeleton />}>
        <ProductInfo id={params.id} />
      </Suspense>
      <Suspense fallback={<PriceSkeleton />}>
        <LivePrice id={params.id} />
      </Suspense>
    </div>
  );
}

Ce qu'il faut retenir de ce pattern :

  • Tout ce qui est en dehors d'un Suspense et résolvable au build entre dans le shell statique.
  • Chaque Suspense crée un trou dont le fallback est rendu instantanément, puis remplacé par le contenu réel.
  • use cache sur ProductInfo évite de réinterroger la base à chaque requête, contrairement à LivePrice qui reste recalculé.

Un point important souligné par la documentation : si un composant accède à des données non mises en cache sans être enveloppé dans un Suspense ni marqué use cache, Next.js lève une erreur explicite au build (« données non mises en cache accédées hors Suspense »). Ce garde-fou vous force à être explicite sur la nature de chaque zone — une contrainte saine qui évite les surprises en production.


Quels sont les cas d'usage concrets du PPR ?

Le PPR n'est pas une optimisation universelle ; il brille surtout quand une page mélange des contenus de natures différentes. Voici les scénarios où il apporte le plus de valeur :

  • E-commerce : catalogue et fiches produit en shell statique, prix, stock et panier en trous dynamiques. Le visiteur voit la page produit instantanément, le prix se met à jour juste après.
  • Tableaux de bord (dashboards) : structure, menus et widgets stables dans le shell ; données utilisateur, métriques temps réel et notifications streamées. L'interface paraît immédiatement utilisable.
  • Médias et blogs avec personnalisation : l'article (statique, mis en cache) part dans le shell ; les blocs « recommandé pour vous » ou « commentaires récents » restent dynamiques.
  • Pages de réservation / disponibilités : la description du service est statique, les créneaux disponibles sont temps réel.

Le dénominateur commun : une majorité de contenu stable ponctuée de quelques zones réellement volatiles. Dans ce cas, le PPR maximise la part servie instantanément depuis le CDN tout en gardant la fraîcheur là où elle compte. Si la page entière est purement statique ou purement dynamique, le PPR n'apporte que peu — d'autres stratégies suffisent.

Quand le PPR n'est-il pas le bon choix ?

Il est tout aussi important de savoir quand ne pas sortir le PPR. Une page purement éditoriale sans personnalisation — page légale, page « à propos », landing entièrement cacheable — ne gagne rien à des trous dynamiques : la génération statique pure est plus simple et tout aussi rapide. De même, un tableau de bord fortement authentifié où presque tout dépend de l'utilisateur connecté n'a que très peu à mettre dans un shell statique utile ; un rendu serveur classique avec un bon cache est alors souvent plus lisible à raisonner.

Le PPR ajoute aussi une charge conceptuelle. Votre équipe doit comprendre les frontières Suspense, la sémantique de use cache, et la différence entre livraison et calcul. Sur un petit projet aux délais serrés, cette courbe d'apprentissage peut ne pas en valoir la peine. La règle honnête : adoptez le PPR quand une page mélange réellement contenu stable et volatil et que le gain de performance est mesurable — pas parce que c'est la fonctionnalité la plus récente. Choisir le bon outil pour la bonne page, c'est ce qui sépare une architecture propre d'une complexité prématurée.


Comment migrer une page existante vers le PPR sans tout casser ?

La bonne nouvelle, c'est que le PPR se prête particulièrement bien à une adoption progressive. Inutile de réécrire toute votre application d'un coup : la stratégie recommandée est incrémentale, page par page, en commençant par celles qui en profiteront le plus.

Voici une démarche prudente, validée par l'expérience :

  1. Identifier les bonnes candidates. Repérez vos pages à fort trafic qui mélangent beaucoup de contenu stable et quelques zones dynamiques : pages produit, articles avec blocs personnalisés, pages de catégorie. Ce sont elles qui offriront le meilleur rapport effort/gain.
  2. Cartographier le statique et le dynamique. Sur chaque page, listez précisément ce qui ne change jamais (layout, header, description), ce qui change rarement (fiche produit) et ce qui est strictement temps réel (prix, panier, données utilisateur). Cette classification est le cœur du travail ; tout le reste en découle.
  3. Encadrer les zones dynamiques. Enveloppez chaque zone temps réel dans une frontière <Suspense> avec un fallback soigné (un skeleton réaliste, pas un simple « Chargement… »). Le fallback fait partie du shell : il doit donner une impression de page déjà construite.
  4. Mettre en cache le stable. Appliquez use cache aux composants qui n'ont pas besoin d'être recalculés à chaque requête, en réglant la durée de validité avec cacheLife selon la fraîcheur tolérée.
  5. Mesurer avant/après. Comparez le TTFB, le LCP et le ressenti réel sur des données de production. Ne déployez que si les chiffres confirment le gain.

Cette approche limite drastiquement le risque : vous validez le comportement sur une page, vous mesurez, puis vous généralisez. C'est exactement la philosophie que nous appliquons sur les projets clients pour fiabiliser une migration sans interruption de service.

Le Partial Pre-Rendering est-il compatible avec le SEO ?

C'est une question légitime : si une partie du contenu est streamée après le shell, les moteurs de recherche la voient-ils ? La réponse, en pratique, est rassurante — à condition de bien placer la frontière entre statique et dynamique.

Le shell statique est par nature parfaitement indexable : c'est du HTML pré-rendu, servi immédiatement, qui contient l'essentiel de votre contenu structurant (titres, texte principal, métadonnées). Pour le référencement, l'idéal est donc que le contenu important pour le SEO se trouve dans le shell, et non dans les trous dynamiques. Un titre de produit, une description, un article de blog : tout cela gagne à être pré-rendu ou mis en cache via use cache, donc présent dès la première réponse HTML.

À l'inverse, les zones réservées aux trous dynamiques — prix temps réel, panier, recommandations personnalisées — ne sont généralement pas critiques pour le référencement. Les laisser streamées ne pénalise pas l'indexation du cœur de page. Le streaming via Suspense étant une technique standard du web moderne, les crawlers savants savent suivre la réponse complète.

La règle d'or est donc simple : placez dans le shell ce que vous voulez voir indexé, gardez dans les trous ce qui est volatile ou personnalisé. Bien découpé, le PPR est non seulement compatible avec le SEO, mais il le sert : un shell servi instantanément améliore les signaux de vitesse, eux-mêmes pris en compte dans l'expérience de page. Pour aller plus loin sur l'optimisation globale, combinez cette approche avec les leviers d'optimisation des performances évoqués plus haut.

Quel est l'impact du PPR sur les performances (TTFB, LCP) ?

L'intérêt principal du PPR se mesure sur deux axes : le TTFB (Time To First Byte) et le LCP (Largest Contentful Paint).

Parce que le shell statique est pré-généré et servable depuis le CDN, le premier octet arrive très vite : le serveur n'a pas à calculer toute la page avant de répondre. Le contenu structurant (souvent le LCP) se trouvant dans le shell, il s'affiche tôt, pendant que les zones dynamiques se remplissent en arrière-plan. À l'inverse, en SSR pur, le navigateur attend que toute la page soit calculée avant le premier rendu.

À titre illustratif (les chiffres dépendent fortement de votre application, de votre infrastructure et de vos données — il ne s'agit pas d'un benchmark mesuré), voici l'ordre d'idée de l'amélioration typiquement visée sur les Core Web Vitals lorsqu'une page mixte passe d'un rendu purement dynamique à un rendu PPR bien découpé :

Core Web Vitals avant/après activation du PPR — valeurs illustrativesIllustratif : sur une page mixte, le PPR vise à réduire le LCP et le CLS en servant un shell stable immédiatement. Les valeurs réelles dépendent de votre application et ne constituent pas un benchmark mesuré.

Attention toutefois à ne pas survendre l'effet : comme expliqué plus haut, le shell statique optimise la livraison, pas le calcul serveur. Si vos zones dynamiques restent lourdes à calculer, le temps total de rendu complet ne change pas forcément ; ce qui change, c'est la vitesse à laquelle l'utilisateur voit quelque chose d'utile. Pour des gains de performance plus larges, au-delà du seul rendu, notre guide diviser le temps de chargement par 10 grâce à l'optimisation des performances couvre les autres leviers (images, bundles, requêtes).


PPR vs ISR vs SSR : quelle stratégie de rendu choisir ?

Le PPR ne rend pas les autres stratégies obsolètes : il les compose. Le bon réflexe est de raisonner par composant, pas par page.

CritèreSSRISRPPR
Vitesse perçueMoyenneRapideTrès rapide (shell instantané)
Fraîcheur des donnéesTemps réelDifférée (revalidation)Temps réel sur les trous dynamiques
Charge serveurÉlevéeFaible à moyenneVariable (faible sur le shell)
GranularitéPagePageComposant
Cas idéalTout temps réelTout semi-statiquePage mixte statique + dynamique

Pour vous aider à trancher au cas par cas, voici un arbre de décision synthétique :

Arbre de décision — quelle stratégie de rendu Next.js choisirArbre de décision : si le contenu est identique pour tous, optez pour SSG ou une revalidation type ISR ; s'il est partiellement personnalisé, le PPR combine shell statique et trous dynamiques ; s'il est entièrement temps réel, le SSR reste pertinent.

En résumé : si une page est entièrement figée, restez en statique pur ; si elle est entièrement temps réel et personnalisée, le SSR reste pertinent ; mais dès qu'elle mélange les deux — ce qui est le cas le plus fréquent — le PPR est généralement le meilleur compromis. Pour comprendre comment ces choix s'inscrivent dans l'évolution récente du framework, voyez aussi Next.js 16.2 Agent-Ready.


Quels sont les pièges et limitations du PPR ?

Adopter le PPR sans en connaître les angles morts mène à des déceptions. Voici les points de vigilance les plus importants.

1. Le shell statique n'évite pas le recalcul serveur. C'est le piège n°1. Comme confirmé par les discussions de la communauté Next.js, le serveur traverse l'arbre complet à chaque requête pour résoudre les composants et savoir quoi streamer, même avec le PPR. Le shell est livré plus tôt, mais le code des layouts et des pages s'exécute toujours. Pour réellement éviter la réexécution, il faut use cache.

2. Tout accès dynamique non encadré casse le build. Lire cookies(), headers(), searchParams, ou faire un fetch non mis en cache hors d'un Suspense déclenche une erreur. C'est voulu, mais cela impose de structurer rigoureusement vos frontières dès le départ.

3. Le statut évolue vite. Entre experimental.ppr (Next.js 15) et cacheComponents (versions récentes), la configuration a changé. Vérifiez toujours la version exacte de votre projet avant de copier un exemple — un tutoriel de 2024 peut être trompeur.

4. Le découpage Suspense demande de la réflexion. Trop de petites frontières fragmentent l'expérience (skeletons partout) ; trop peu, et vous perdez le bénéfice du streaming. Le bon découpage suit la structure réelle des données.

5. Ce n'est pas une fonctionnalité « set and forget ». Le PPR doit être validé en préproduction, avec de vraies données et de vraies charges. Mesurez avant/après plutôt que de supposer un gain.

Ces limites ne disqualifient pas le PPR — elles rappellent que c'est un outil avancé. Bien utilisé, il offre le meilleur des deux mondes ; mal compris, il peut donner l'illusion d'un gain inexistant.


Conclusion

Le Partial Pre-Rendering est plus qu'une optimisation : c'est un changement de granularité dans la façon de penser le rendu web. En passant de la page au composant comme unité de décision, Next.js vous permet de servir un shell statique instantané tout en gardant vos données temps réel exactement là où elles comptent. Le résultat : une perception de vitesse nettement améliorée, sans sacrifier la fraîcheur.

Retenez les trois idées clés : le PPR compose les stratégies de rendu plutôt que de les remplacer ; Suspense gère la livraison quand use cache gère le calcul ; et le statut de la fonctionnalité évolue — vérifiez toujours votre version. Pour replacer tout cela dans le contexte global de la plateforme, lisez notre article d'introduction à Next.js 16 et React 19.


Besoin d'aide pour migrer vers le PPR ou optimiser vos Core Web Vitals ? Chez BOVO Digital, nous concevons et optimisons des applications web performantes avec les dernières technologies Next.js. Parlons de votre projet.

Étiquettes

#Next.js#PPR#Performance#React#use cache#Server Components#Web Vitals#Cache Components

Partager cet article

LinkedInX

FAQ

Le Partial Pre-Rendering de Next.js est-il stable ou expérimental ?

Selon la documentation Next.js récente, le PPR n'est plus une option isolée : il est devenu le modèle de rendu par défaut quand on active Cache Components (`cacheComponents: true`). L'ancien drapeau `experimental.ppr` de Next.js 15 reste accepté pour la compatibilité mais est superseded. Le PPR doit donc être considéré comme une fonctionnalité avancée à valider en préproduction avant tout déploiement.

Quelle différence entre Suspense et la directive use cache ?

Ce sont deux mécanismes complémentaires. `<Suspense>` gère le timing de livraison : il définit les « trous » dynamiques streamés après le shell. La directive `use cache` gère le calcul : elle mémorise la valeur de retour d'une fonction ou d'un composant pour éviter de la recalculer. Pour des performances maximales, on combine les deux : Suspense pour la livraison instantanée du shell, `use cache` pour ne pas réexécuter les parties stables.

Le PPR remplace-t-il vraiment SSR, SSG et ISR ?

Le PPR ne supprime pas ces stratégies, il les compose. Une même page peut contenir des zones pré-rendues (équivalent SSG), des zones revalidées (équivalent ISR) et des zones dynamiques streamées (équivalent SSR), le tout dans un seul rendu. On choisit toujours la bonne stratégie par composant ; le PPR permet simplement de les mélanger sur la même page.

Comment améliorer les Core Web Vitals de mon site Next.js ?

Les leviers principaux sont : optimisation des images (WebP/AVIF, lazy loading, dimensions explicites), suppression des ressources bloquantes, report des scripts tiers, et un modèle de rendu adapté comme le PPR pour servir un shell statique instantané. BOVO Digital réalise des audits PageSpeed pour identifier vos axes d'amélioration prioritaires.

Combien coûte le développement d'une application web sur mesure ?

Un site vitrine premium démarre à 1 500€, un SaaS ou une application web complexe entre 8 000€ et 30 000€ selon les fonctionnalités. BOVO Digital fournit des devis gratuits et détaillés sous 48h.

Prêt à l'implémenter ?

Réservez un appel stratégique gratuit de 30 min avec nos experts

Nous analyserons votre situation et proposerons un plan d'action concret.

William Aklamavo

Expert en développement web et automatisation, passionné par l'innovation technologique et l'entrepreneuriat digital.

Passez à l'action avec BOVO Digital

Cet article vous a donné des idées ? Nos experts vous accompagnent de la stratégie à la mise en production.

Articles similaires