Réduire le TTFB d'une application Next.js en production

Le Time To First Byte conditionne directement le Largest Contentful Paint et, par ricochet, le taux de conversion. Sur une application Next.js en production, un TTFB supérieur à 600 ms révèle presque toujours un problème d'architecture identifiable : rendu bloquant, requêtes séquentielles, absence de cache au bon niveau. Voici les leviers concrets, classés par ordre d'impact.

Identifier la source réelle avant d'optimiser

Ne touchez à rien avant d'avoir isolé la cause. Le TTFB agrège plusieurs temps distincts : latence réseau, temps de function cold start, temps de rendu serveur, temps de requêtes base de données. Utilisez le header Server-Timing pour les décomposer :

export async function getServerSideProps() {
  const start = Date.now();
  const data = await fetchData();
  return {
    props: { data },
  };
}

Sur Vercel, activez les traces via @vercel/otel pour visualiser la répartition dans le dashboard d'observabilité. Un TTFB dégradé causé par une cold start (fonction serverless froide, 300-800 ms) ne se traite pas comme un TTFB dégradé causé par une requête SQL non indexée. Confondre les deux fait perdre des semaines de sprint.

Éliminer le rendu bloquant côté serveur

getServerSideProps et les Server Components qui attendent des données non critiques sont la première cause de TTFB élevé. Deux actions immédiates :

  • Streaming avec Suspense : découpez la page en segments et laissez le shell arriver en premier pendant que les données lentes se chargent en arrière-plan.
export default function Page() {
  return (
    <Layout>
      <Suspense fallback={<Skeleton />}>
        <SlowDataComponent />
      </Suspense>
    </Layout>
  );
}
  • Parallélisation des fetchs : remplacez les await séquentiels par Promise.all. Un enchaînement de trois appels à 150 ms chacun coûte 450 ms en série contre 150 ms en parallèle.

Si une donnée n'est pas nécessaire au premier rendu (analytics, recommandations, compteurs), sortez-la du chemin critique et chargez-la côté client après hydratation.

Configurer le cache à chaque niveau de la chaîne

La majorité des TTFB élevés en production viennent d'un cache mal configuré, pas d'un manque de puissance serveur.

ISR (Incremental Static Regeneration) : pour les pages dont le contenu change peu souvent, forcez la régénération en arrière-plan plutôt que le rendu à la demande :

export const revalidate = 60; // secondes

Cache HTTP explicite sur les routes API et les Route Handlers :

export async function GET() {
  const data = await getData();
  return Response.json(data, {
    headers: {
      'Cache-Control': 's-maxage=60, stale-while-revalidate=300',
    },
  });
}

stale-while-revalidate est le paramètre le plus sous-utilisé : il permet de servir une réponse en cache instantanément pendant que la régénération se fait en tâche de fond, garantissant un TTFB proche de zéro sur le CDN edge.

Fetch cache Next.js : vérifiez que vos appels fetch ne sont pas accidentellement en no-store. Depuis Next.js 15, le comportement par défaut a changé — auditez explicitement chaque appel avec { cache: 'force-cache' } ou une stratégie de revalidation définie.

Traiter le cold start et la proximité géographique

Sur Vercel, les fonctions serverless en région unique pénalisent les utilisateurs éloignés. Deux options :

  • Edge Runtime pour les routes qui n'ont pas besoin de Node.js complet (auth légère, redirections, personnalisation géographique) :
export const runtime = 'edge';

Le cold start Edge est de l'ordre de quelques millisecondes contre plusieurs centaines pour une fonction Node classique.

  • Déploiement multi-région avec regions dans vercel.json si votre base de données le permet, en gardant la fonction serverless proche de la base de données plutôt que de l'utilisateur — la latence base de données pèse généralement plus que la latence réseau utilisateur-serveur.

Auditer la couche base de données et les dépendances externes

Un TTFB de 800 ms cache souvent une requête ORM non optimisée. Vérifiez :

  • Connection pooling : sur des fonctions serverless, chaque invocation peut ouvrir une nouvelle connexion PostgreSQL. Utilisez PgBouncer ou un pooler managé (Neon, Supabase Pooler) pour éviter la saturation.
  • N+1 queries : un include Prisma mal maîtrisé multiplie les allers-retours. Passez en revue les requêtes générées avec prisma.$on('query', ...) en environnement de staging.
  • Appels API tiers synchrones dans le chemin de rendu (paiement, CRM, analytics) : déplacez-les en tâche asynchrone ou en Server Action

Le Template Puppeteer B2B, gratuit

Un script d'automatisation prêt à l'emploi pour vos scrapers et générateurs de PDF B2B. Envoyé par e-mail, sans spam.

Partenaire

Hébergez vos APIs sur une infrastructure rapide

Hostinger propose un hébergement cloud et VPS haute performance avec NVMe, CDN intégré et déploiement Git — idéal pour vos back-ends et APIs JSON.