Next.js 16 Partial Prerendering : Landing pages SaaS instantanées
Apprenez à implémenter le Partial Prerendering stable de Next.js 16 pour créer des landing pages SaaS à chargement instantané alliant coque statique et données dynamiques.

Que sont les Cache Components de Next.js 16 ?
Les Cache Components représentent une architecture de rendu qui combine une sortie pré-rendue mise en cache avec des composants dynamiques et spécifiques à l'utilisateur diffusés en continu (streaming). Cette approche prolonge l'idée du pré-rendu partiel (Partial Prerendering) à travers le nouveau modèle de Cache Components.
Pour les pages de destination (landing pages) SaaS, cela est crucial car les tarifs, les appels à l'action (CTA) personnalisés selon la session, la devise de facturation et les liens vers le tableau de bord nécessitent souvent un contexte d'exécution au moment de la requête, sans pour autant forcer l'intégralité de la route à utiliser le SSR classique.
Pendant des années, les développeurs concevant des pages de destination SaaS devaient souvent choisir entre tout pré-générer pour optimiser la vitesse (génération de site statique ou SSG) ou tout récupérer sur le serveur (rendu côté serveur ou SSR) pour obtenir un contexte dynamique. Ce choix binaire imposait aux équipes des solutions de contournement complexes impliquant des hooks de récupération de données côté client et des décalages de mise en page (layout shifts).
Avec la sortie de Next.js 16, ce compromis architectural est réduit. En exploitant les capacités de streaming de React et la sémantique de mise en cache de l'App Router, le framework s'appuie sur les frontières React <Suspense> pour différer les arbres de composants dynamiques. Les parties de la route pouvant être mises en cache peuvent être pré-rendues dans une structure statique (static shell), tandis que les parties non mises en cache ou liées au moment de la requête sont différées derrière des frontières Suspense.
TL;DR
Les Cache Components de Next.js 16 vous permettent de combiner une sortie pré-rendue mise en cache avec des fragments dynamiques diffusés en continu. Activez-les avec cacheComponents: true, puis utilisez la directive use cache pour mettre explicitement en cache des pages, des composants ou des fonctions asynchrones. Lorsque les Cache Components sont activés, la récupération de données est dynamique par défaut, à moins d'opter pour la mise en cache. Utilisez cacheLife() pour définir la fraîcheur, cacheTag() pour étiqueter la sortie mise en cache, updateTag() à l'intérieur des Server Actions pour une invalidation immédiate de type "read-your-own-writes" (lecture après écriture), et revalidateTag(tag, 'max') pour les mises à jour en arrière-plan (stale-while-revalidate). La variante starting: de Tailwind CSS v4 peut animer les éléments nouvellement insérés à l'aide de la règle native @starting-style, mais elle n'est pas spécifique au streaming Suspense.
Au-delà du SSG : pourquoi les Cache Components sont la nouvelle référence
Historiquement, concevoir une page de destination ultra-performante impliquait de s'en tenir strictement à la génération de site statique (SSG). Cependant, les applications SaaS modernes exigent un contexte dynamique. Vous pouvez avoir besoin d'afficher la devise de facturation spécifique d'un utilisateur, de mettre en avant une offre entreprise personnalisée ou de modifier un appel à l'action principal si une session active est détectée. Réaliser cela exclusivement côté client entraîne une expérience utilisateur complexe, tandis que le faire côté serveur peut ralentir la réponse initiale.
Le modèle des Cache Components modifie la façon dont nous associons les chemins de routage (route paths) aux stratégies de rendu. Au lieu de classifier une page entière comme statique ou dynamique, le moteur de rendu prend des décisions granulaires au niveau du composant. Si un composant dépend de données au moment de la requête, il est isolé. Le layout externe est servi avec une faible latence, permettant à votre application d'atteindre un excellent temps de réponse initial (Time to First Byte ou TTFB).
Lorsque l'on s'appuie uniquement sur le SSR classique, Next.js doit attendre la récupération de données la plus lente avant d'envoyer le premier octet de code HTML au navigateur, ce qui retarde le chargement des polices et la détection des images. Avec les Cache Components de Next.js 16, le navigateur reçoit rapidement la structure statique (shell), débloquant la découverte des ressources pendant que le serveur traite la logique de tarification personnalisée.
| Stratégie de rendu | Chargement initial (TTFB) | Impact SEO | Personnalisation dynamique | Granularité du cache |
|---|---|---|---|---|
| Legacy SSG | Rapide | Excellent | Requiert une récupération côté client | Au niveau de la route |
| Legacy SSR | Dépendant de la récupération la plus lente | Excellent | Native | Au niveau de la route |
| Next.js 16 Cache Components | Rapide lorsque la structure (shell) est pré-rendue | Excellent lorsque le contenu critique est dans la structure | Streaming | Au niveau du composant/de la fonction |
Activer cacheComponents sur votre page de destination SaaS
La mise à niveau vers Next.js 16 apporte plusieurs améliorations structurelles dans l'orchestration de la mise en cache et du pré-rendu. Dans les versions expérimentales précédentes, les développeurs s'appuyaient sur l'option experimental.ppr pour tester le rendu hybride. Cette option a été supprimée au profit d'un modèle de cache unifié.
Pour activer les Cache Components dans Next.js 16, vous devez modifier la configuration de votre framework :
- Passez à Next.js 16 et React 19.
- Retirez toutes les options
experimental.pprde votre configuration. - Activez la fonctionnalité
cacheComponentspour formaliser l'analyse de la structure statique (static shell).
// next.config.ts
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
// Active les Cache Components et la directive "use cache"
cacheComponents: true,
// Recommandé : applique des types et avertissements stricts pour React 19
reactStrictMode: true,
};
export default nextConfig;
Par ailleurs, Next.js 16 renomme la convention middleware.ts en proxy.ts. Les fonctionnalités restent similaires, mais ce nouveau nom reflète mieux son rôle à la frontière du réseau et de la requête. En adoptant la convention proxy.ts, vous définissez une séparation plus claire entre vos opérations de requête et votre logique de rendu de composants. Notez également que proxy.ts utilise l'environnement d'exécution (runtime) Node.js, et non Edge.
Le modèle de structure statique (Static Shell) : une architecture pour un faible TTFB
Le mécanisme fondamental des Cache Components repose sur le modèle de structure statique (static shell). Les composants situés en dehors de la frontière dynamique Suspense peuvent être inclus dans la structure statique pré-rendue. Lorsque la route est éligible au pré-rendu, la structure externe peut être servie comme une sortie pré-rendue statique, tandis que les parties dynamiques sont diffusées en continu par la suite.
Pour implémenter cela efficacement sur une page de destination SaaS, vous devez analyser soigneusement les endroits où vous appelez des fonctions dynamiques. Les contraintes officielles lors de l'utilisation de Next.js 16 incluent :
- Contraintes de données : Les fonctions mises en cache ne peuvent pas accéder directement à
cookies(),headers()ousearchParams; lisez-les en dehors du scope mis en cache et transmettez-les en tant que paramètres. - Contraintes de rendu : De plus, si vous utilisez le hook côté client
useSearchParams()sans envelopper le composant consommateur dans une frontière<Suspense>, l'intégralité du chemin de routage (route path) basculera en rendu côté client (client-side rendering).
Note : Next.js fournit également 'use cache: private', qui permet d'accéder à des API spécifiques aux requêtes comme cookies(), headers() et searchParams au sein d'un scope mis en cache. Cependant, son utilisation n'est pas recommandée en production, et les résultats sont mis en cache uniquement dans la mémoire du navigateur. Pour les pages de destination SaaS en production, la méthode la plus sûre consiste toujours à lire les données de requête en dehors des scopes 'use cache' standards et à passer des valeurs sérialisables aux fonctions mises en cache.
Construire la frontière Suspense
Voici comment construire une structure statique robuste qui préserve votre TTFB tout en chargeant dynamiquement des liens personnalisés vers le tableau de bord :
// app/page.tsx
import { Suspense } from 'react';
import { HeroSection } from '@/components/hero';
import { FeatureGrid } from '@/components/features';
import { UserNavigation } from '@/components/user-navigation';
import { NavigationSkeleton } from '@/components/skeletons';
export default function LandingPage() {
return (
<main className="flex min-h-screen flex-col">
{/* STRUCTURE STATIQUE : Éligible au pré-rendu */}
<header className="flex w-full items-center justify-between p-6">
<div className="text-xl font-bold">SaaS Co</div>
{/* SEGMENT DYNAMIQUE : Diffusé en continu au moment de la requête */}
<Suspense fallback={<NavigationSkeleton />}>
<UserNavigation />
</Suspense>
</header>
{/* STRUCTURE STATIQUE : Éligible au pré-rendu */}
<HeroSection />
<FeatureGrid />
</main>
);
}
Dans cette référence architecturale, le composant UserNavigation est le seul segment qui lit des données dynamiques. Parce qu'il se trouve à l'intérieur d'une frontière Suspense, le chemin de routage de LandingPage compile avec succès une structure statique. Le rendu initial atteint rapidement le navigateur, affichant une page marketing complète tandis que le segment de navigation personnalisé se charge indépendamment en streaming.
Utiliser "use cache" pour gérer des tarifs SaaS dynamiques
La directive use cache permet d'opter explicitement pour la mise en cache de pages, de composants ou de fonctions asynchrones. Avec cacheComponents: true, la récupération de données est dynamique par défaut, sauf si vous marquez un traitement spécifique comme pouvant être mis en cache.
Lors de la création d'une page de tarifs SaaS avec des Cache Components, vous récupérez souvent les données des différentes offres à partir d'un CMS ou d'une base de données externe. Vous souhaitez que ces données fassent partie de la structure statique, tout en gardant le contrôle sur leur durée de validité.
Définir des profils de cache explicites
En appliquant use cache aux côtés d'un profil cacheLife et d'un cacheTag, vous pouvez définir explicitement la durée pendant laquelle le composant de tarification reste valide avant de nécessiter une revalidation en arrière-plan, tout en l'associant à une étiquette (tag) pour une invalidation future.
// components/pricing-tier.tsx
import { cacheLife, cacheTag } from 'next/cache';
import { db } from '@/lib/db';
interface PricingData {
id: string;
name: string;
price: number;
}
async function getPricing(tierId: string): Promise<PricingData | null> {
// Indique au framework de mettre en cache la sortie de cette Server Function
'use cache';
cacheTag(`pricing-${tierId}`);
// Définit explicitement le comportement du cache
cacheLife({
stale: 3600, // Servir des données périmées pendant 1 heure
revalidate: 86400, // Rafraîchissement en arrière-plan toutes les 24 heures
expire: 604800, // Expirer complètement après 1 semaine
});
return db.pricing.findUnique({ where: { id: tierId } });
}
export async function PricingTier({ tierId }: { tierId: string }) {
const tier = await getPricing(tierId);
if (!tier) return null;
return (
<div className="rounded-2xl border border-gray-200 p-8 shadow-sm">
<h3 className="text-lg font-semibold">{tier.name}</h3>
<p className="mt-4 text-4xl font-bold">${tier.price}</p>
<button className="mt-6 w-full rounded-md bg-blue-600 px-4 py-2 text-white">
S'abonner maintenant
</button>
</div>
);
}
L'utilisation de cacheLife("hours") est tout à fait valide, mais pour des tarifs critiques ou du contenu de page de destination stratégique, un objet de configuration explicite rend la politique de mise en cache plus lisible pour votre équipe. Les données tarifaires s'intègrent ainsi dans la structure statique, conservant leur vitesse d'affichage tout en garantissant que les tarifs se mettent à jour de manière systématique.
Invalidation du cache : utiliser updateTag et revalidateTag efficacement
Même avec des configurations de cache robustes, il y a des moments où vous devez purger manuellement le cache. Un scénario SaaS classique survient lorsqu'un administrateur met à jour la liste des fonctionnalités d'un produit et souhaite que la page de destination reflète immédiatement ce changement.
Il est crucial de bien faire la distinction entre les différentes API d'invalidation de cache disponibles dans Next.js 16 :
updateTags'utilise uniquement dans les Server Actions et expire immédiatement les données mises en cache. N'appelez pasupdateTag()depuis des Route Handlers ou depuis un Proxy.revalidateTag(tag, 'max')est la syntaxe recommandée pour le comportement "stale-while-revalidate".- La forme à argument unique
revalidateTag(tag)est un modèle hérité (legacy). Pour garantir une mise à jour immédiate lors d'une action utilisateur (lecture après écriture), utilisezupdateTag(tag)à l'intérieur des Server Actions. Pour un comportement de type "stale-while-revalidate", utilisezrevalidateTag(tag, 'max').
Étiqueter les lectures mises en cache
Tout d'abord, assurez-vous que votre lecture mise en cache associe les données à une étiquette à l'aide de cacheTag() :
// lib/features.ts
import { cacheLife, cacheTag } from 'next/cache';
import { db } from '@/lib/db';
export async function getLandingFeatures() {
'use cache';
cacheTag('landing-features');
// La configuration explicite est préférable à cacheLife('hours')
cacheLife({
stale: 3600,
revalidate: 86400,
expire: 604800,
});
return db.features.findMany();
}
Invalidation en arrière-plan avec revalidateTag
Utilisez revalidateTag(tag, 'max') dans les Server Actions ou les Route Handlers lorsqu'un léger délai est acceptable, déclenchant ainsi un rafraîchissement "stale-while-revalidate" en arrière-plan.
// app/api/revalidate-products/route.ts
import { revalidateTag } from 'next/cache';
export async function POST() {
revalidateTag('landing-features', 'max');
return Response.json({ revalidated: true });
}
Invalidation au premier plan avec updateTag
Cependant, si vous exécutez une Server Action et avez besoin que l'utilisateur voie le contenu mis à jour immédiatement après une modification réussie (lecture après écriture), Next.js 16 utilise updateTag(tag).
// app/actions.ts
'use server';
import { updateTag } from 'next/cache';
import { db } from '@/lib/db';
export async function updateFeatureFlag(featureId: string, enabled: boolean) {
// 1. Modifier la base de données
await db.features.update({
where: { id: featureId },
data: { enabled },
});
// 2. Purger immédiatement les données mises en cache à la demande
updateTag('landing-features');
return { success: true };
}
L'utilisation d' updateTag force Next.js à rejeter immédiatement les données mises en cache. Combiné avec les Cache Components, cela garantit que vos composants dynamiques reflètent fidèlement l'état de la base de données, sans sacrifier les performances de la structure statique environnante.
Habiller les zones d'attente ("Holes") : Squelettes avec Tailwind CSS v4 et @starting-style
Lorsqu'une page de destination SaaS utilise le streaming Suspense pour afficher des blocs dynamiques au sein d'une structure statique, la gestion de la transition visuelle est cruciale. Un décalage de mise en page (layout shift) nuit à l'expérience utilisateur de l'application.
Historiquement, les développeurs se tournaient vers des bibliothèques d'animation JavaScript pour gérer les séquences de montage de composants. Avec Next.js 16 et Tailwind CSS v4, ce n'est plus nécessaire. La variante starting: de Tailwind CSS v4 prend en charge la fonctionnalité CSS native @starting-style. Cela s'applique pleinement à Tailwind CSS v4, qui utilise un modèle axé d'abord sur le CSS. C'est particulièrement utile pour les interfaces diffusées en streaming ou fraîchement insérées, vous permettant de créer des transitions pour les éléments à mesure qu'ils apparaissent sans recourir à JavaScript, bien qu'il s'agisse d'une fonctionnalité purement CSS et non spécifique à Suspense.
// components/user-profile.tsx
export function UserProfile({ username }: { username: string }) {
return (
// La variante starting: gère nativement l'état du DOM avant montage
<div className="transition-all duration-500 ease-out starting:scale-95 starting:opacity-0 scale-100 opacity-100">
<span className="text-sm font-medium text-slate-700">
Ravi de vous revoir, {username}
</span>
</div>
);
}
La barre de navigation externe faisant partie de la structure statique, elle s'affiche rapidement à l'écran. Quelques instants plus tard, le composant UserProfile est résolu sur le serveur et arrive en streaming dans le navigateur. Tailwind CSS v4 interprète la variante starting:, déclenchant un effet d'apparition et de mise à l'échelle via des transitions CSS natives. Cette approche allège le volume de JavaScript exécuté sur le thread principal, ce qui améliore les performances.
Erreurs courantes
Lors de l'adoption des Cache Components de Next.js 16, les équipes de développement rencontrent fréquemment quelques erreurs structurelles :
- Lire des API dynamiques directement au sein de scopes mis en cache → Les fonctions mises en cache ne peuvent pas accéder directement à
cookies(),headers()ousearchParams; lisez-les en dehors du scope mis en cache et transmettez-les en tant que paramètres. Un accès direct provoque une erreur car ces API ne peuvent pas être sérialisées. - Utiliser
useSearchParams()sans<Suspense>→ Bascule l'intégralité du chemin de routage vers le rendu côté client. Solution : Enveloppez le composant spécifique consommant le hook dans une frontière<Suspense>. - Utiliser la forme à argument unique
revalidateTag('my-tag')→ Cette syntaxe est un modèle hérité (legacy). Solution : Pour garantir la lecture après écriture, utilisezupdateTag('my-tag')à l'intérieur des Server Actions. Pour un comportement de type "stale-while-revalidate", utilisezrevalidateTag('my-tag', 'max'). - S'en remettre entièrement aux profils textuels comme
cacheLife("hours")→ L'utilisation decacheLife("hours")est valide, mais un objet de configuration explicite tel quecacheLife({ stale, revalidate, expire })rend la politique de mise en cache bien plus lisible pour votre équipe. - Utiliser
middleware.tspour la logique de routage → Cette convention de fichier est remplacée parproxy.tsdans l'architecture de Next.js 16. Solution : Migrez l'interception de routage basée sur l'hôte ou multi-tenant vers la nouvelle convention de fichierproxy.ts.
FAQ
Comment activer les Cache Components dans Next.js 16 ?
Vous activez cette architecture en définissant cacheComponents: true dans votre fichier next.config.ts. Cela remplace l'ancienne configuration experimental.ppr par le modèle de Cache Components et permet à Next.js d'identifier les sorties pré-rendues pouvant être mises en cache ainsi que les frontières dynamiques.
Quelle est la différence entre les Cache Components et le streaming Suspense ?
Le streaming avec Suspense est un mécanisme React que Next.js utilise conjointement avec les Cache Components de manière à combiner une sortie pré-rendue avec des fragments dynamiques diffusés en continu.
Est-ce que cette approche améliore les Core Web Vitals ?
Elle peut améliorer le TTFB et le LCP lorsque la structure statique (shell) contient le contenu critique situé au-dessus de la ligne de flottaison (above-the-fold) et que les traitements dynamiques sont isolés derrière Suspense. Étant donné que la structure HTML statique est servie rapidement, le navigateur peut découvrir et commencer à télécharger des ressources critiques — comme l'image principale (hero image) et les feuilles de style principales — bien avant que le serveur ne termine les opérations de données dynamiques.
Puis-je utiliser cookies() à l'intérieur d'un composant mis en cache ?
Non. Les fonctions mises en cache ne peuvent pas accéder directement à cookies(), headers() ou searchParams ; lisez-les en dehors du scope mis en cache et passez-les en paramètres. Un accès direct au sein d'une frontière use cache lèvera une erreur. De plus, si vous utilisez le hook côté client useSearchParams() sans frontière <Suspense>, le chemin de routage basculera en rendu côté client.
Conclusion
Les Cache Components de Next.js 16 redéfinissent la manière dont les équipes conçoivent des applications web optimisées pour la conversion. En maîtrisant le modèle de structure statique (static shell), en déployant de manière stratégique la directive use cache et en isolant les données de session dynamiques dans des frontières Suspense, vous pouvez obtenir des temps de réponse faibles tout en préservant une personnalisation indispensable à votre SaaS.
Le paradigme consistant à devoir choisir exclusivement entre la rapidité du statique et la flexibilité du dynamique est en train de changer. Si vous cherchez à lancer des projets performants en toute fiabilité, l'utilisation de starters prêts pour la production intégrant déjà ces modèles de cache est une excellente solution. Explorez les templates Next.js premium d'Aniq UI pour démarrer votre prochaine page de destination SaaS.


