
Je suis Mathieu Ponton, Co-Founder & ingénieur logiciel chez Apogée Consult à Lyon. Ingénieur diplômé de Polytech Lyon (Informatique), j'ai fait trois ans en apprentissage, partagés entre la Métropole de Lyon (inclusion numérique avec Res'in et sobriété énergétique avec Écolyo) et Superwyze, une startup medtech (POCs, dont certains aujourd'hui industrialisés, et travail sur des codebases existantes). J'ai livré plus de 10 projets en production (web, mobile et IA / RAG) pour des PME, startups et organisations publiques.
Partial Prerendering : production-ready ou pas, ce qu'on a observé en test
- nextjs
On a testé PPR sur un projet réel pendant 6 semaines. Voici ce qui fonctionne, ce qui est encore instable, et ce qu'on recommande avant de l'adopter en production.
Partial Prerendering : production-ready ou pas, ce qu'on a observé en test
PPR est apparu en expérimental dans Next.js 14, est passé en incremental dans Next.js 15. La promesse est réelle : une seule route peut servir un shell statique depuis le CDN, et streamer les parties dynamiques depuis le serveur, sans écrire deux routes séparées.
On a testé pendant 6 semaines sur un projet staging, une application de catalogue produit avec des pages qui mélangent contenu éditorial statique et stock en temps réel.
Comment PPR fonctionne concrètement
Sans PPR, une route est soit statique (générée au build), soit dynamique (rendue à la requête). Le moindre cookies() ou headers() dans le graphe de composants rend toute la route dynamique.
Avec PPR, Next.js identifie au build ce qui peut être prérendu (tout ce qui n'utilise pas de données dynamiques) et ce qui doit être streamé. À la requête, le shell statique arrive depuis le CDN en quelques millisecondes, les parties dynamiques arrivent en streaming depuis le serveur.
// next.config.ts, activer PPR en mode incremental
export default {
experimental: {
ppr: "incremental",
},
}
// app/products/[id]/page.tsx, opt-in par route
export const experimental_ppr = true
export default function ProductPage({ params }) {
return (
<main>
<ProductDescription /> {/* statique, prérendu au build */}
<Suspense fallback={<StockSkeleton />}>
<StockAvailability /> {/* dynamique, streamé à la requête */}
</Suspense>
</main>
)
}L'opt-in par route (experimental_ppr = true) est la chose à retenir du mode incremental. On n'active pas PPR sur tout le projet d'un coup, on choisit les routes qui en bénéficient.
Ce qu'on a observé de positif
Le gain sur le TTFB perçu est immédiat et mesurable. Sur nos pages de catalogue, le shell de la page (hero, navigation, description produit, CTA) arrivait en moins de 50ms depuis Cloudflare. Sans PPR, le même contenu nécessitait 400ms à 600ms de rendu serveur avant le premier octet.
Pour les utilisateurs sur mobile ou connexion dégradée, la différence visuelle est frappante. La page semble chargée avant que le stock soit connu.
La cohabitation avec generateStaticParams fonctionne correctement. On peut statifier les slugs au build et toujours bénéficier du PPR pour les parties dynamiques de chaque page.
Ce qui est encore problématique
Les dépendances implicites créent des surprises
PPR doit résoudre statiquement au build quelles parties de la page sont dynamiques. Si un composant "statique" importe conditionnellement un module qui utilise headers() ou cookies(), PPR peut basculer toute la page en dynamique sans avertissement clair.
On a eu ce cas avec une librairie d'analytics tier qui lisait le user-agent dans certaines conditions. PPR détectait la dépendance dynamique et désactivait silencieusement le prérendu statique pour la route. Le build réussissait, mais les performances étaient identiques à une route dynamique classique.
Le diagnostic est difficile : il faut activer les logs de build PPR explicitement et inspecter la sortie pour vérifier que le shell est bien marqué comme statique.
La gestion du cache sur Cloudflare
PPR génère des réponses HTTP avec des headers Cache-Control non standards. Sur Cloudflare, la configuration par défaut ne reconnaît pas le shell PPR comme cacheable de façon différenciée de la partie dynamique.
On a dû configurer manuellement des règles de cache Cloudflare pour cacher le shell sur les premiers octets de la réponse, et laisser passer le stream pour le reste. C'est faisable, mais pas documenté de façon claire par Vercel pour les infrastructures non-Vercel.
Sur Vercel en revanche, PPR fonctionne sans configuration supplémentaire, l'infrastructure est optimisée pour ce pattern.
La stabilité de l'API expérimentale
L'API a changé entre Next.js 14 et 15. La propriété experimental_ppr sur les routes est arrivée dans 15.x. Si vous avez des tests Playwright ou Cypress qui vérifient le comportement de pages PPR, attendez-vous à devoir les ajuster sur chaque minor Next.js.
On a eu deux changements de comportement sur les skeletons en 6 semaines, des changements qui affectaient le layout shift mesuré par nos tests. Rien de critique, mais suffisamment instable pour ne pas l'embarquer dans une production sans surveillance active.
Notre recommandation actuelle
PPR est production-ready sur Vercel, sur des routes isolées, avec surveillance active.
En dehors de Vercel, il faut du temps de configuration infrastructure. C'est faisable, mais comptez une journée de travail DevOps pour une configuration CDN correcte.
On ne l'active pas encore en global (ppr: true sans incremental), trop de comportements surprenants sur les routes qui n'ont pas été testées individuellement.
Notre pratique : activer PPR uniquement sur les routes où on peut mesurer le gain, pages produit, pages de contenu à fort trafic, et laisser les routes de dashboard et les pages admin en mode dynamique classique.
La question qu'on suit : PPR sortira-t-il de l'expérimental dans Next.js 16 avec une API stabilisée ? Si oui, ça changera notre recommandation pour les projets greenfield.