En 2026, la personnalisation SSR coûte encore cher : transporter le contexte utilisateur vers le serveur d'origine, interroger la base de données, générer le rendu, puis revenir via le CDN. Latence moyenne : 200-300ms. Edge SSR élimine cette boucle — récupérez les données du KV store au point le plus proche de l'utilisateur, générez le rendu, renvoyez. Quelle architecture se cache derrière une latence réduite à 40ms en production ?

L'Économie Gagnée par Edge SSR

Dans une architecture SSR basée sur l'origine, chaque requête suit le même chemin : CDN edge → serveur d'origine → base de données → logique applicative → réponse. L'utilisateur est à 50ms de distance, mais l'origine est à Istanbul et la base de données à Francfort — le round-trip commence à 180ms. Edge SSR inverse cette économie : les Cloudflare Workers ou les Vercel Edge Functions s'exécutent au PoP (Point of Presence) à 15-30ms de l'utilisateur. Quand le KV store est au même emplacement edge, la latence totale chute à 40-60ms.

Le gain ne se limite pas au temps. Les coûts de ressources diminuent aussi. Vous utilisez le serveur d'origine uniquement pour les mutations (POST/PUT/DELETE) ; le trafic GET, représentant 90 %, s'arrête à l'edge. Le cold start chez Vercel Edge est 0-5ms, chez Cloudflare Workers environ 1ms. Dans une architecture SSR traditionnelle, le cold start du conteneur Node.js se situe entre 500-1200ms. Cette différence impacte directement la vitesse de la première interaction.

Sur un site e-commerce, vous pouvez générer le rendu à l'edge des éléments personnalisés : tarification selon l'utilisateur, statut du stock, contenu du panier. Vous cachez le squelette de la page principale en HTML statique et ne remplissez que les blocs dynamiques via Edge SSR — logique « progressive enhancement ». Cette approche hybride élève le taux de cache hits à 85% ; le TTFB (Time to First Byte) descend à 30ms.

Architecture Cloudflare Workers + KV Store

Cloudflare Workers repose sur des isolates V8 — contrairement aux conteneurs traditionnels, chaque requête s'exécute dans son propre sandbox sans état partagé. Le KV store est un stockage clé-valeur eventually-consistent, répliqué globalement. Objectifs de latence : lecture 10-30ms, écriture 100-200ms (à cause de la réplication asynchrone). Configuration :

// worker.js — Point d'entrée Edge SSR
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const userId = getUserId(request); // Récupère depuis cookie

    // Récupère le contexte utilisateur du KV
    const userCtx = await env.USER_KV.get(`user:${userId}`, { type: 'json' });
    
    if (!userCtx) {
      return new Response('Unauthorized', { status: 401 });
    }

    // Génère le rendu de la page personnalisée
    const html = renderPersonalizedPage({
      userName: userCtx.name,
      cart: userCtx.cart,
      recentlyViewed: userCtx.recentlyViewed,
    });

    return new Response(html, {
      headers: {
        'Content-Type': 'text/html;charset=UTF-8',
        'Cache-Control': 'private, max-age=0',
      },
    });
  },
};

function renderPersonalizedPage(data) {
  // Logique de template simple — en production, Vue/React render
  return `
    <!DOCTYPE html>
    <html>
      <head><title>Bienvenue ${data.userName}</title></head>
      <body>
        <h1>Bonjour ${data.userName}</h1>
        <p>Vous avez ${data.cart.length} articles dans votre panier</p>
        <ul>
          ${data.recentlyViewed.map(p => `<li>${p}</li>`).join('')}
        </ul>
      </body>
    </html>
  `;
}

Structure de données KV :

  • Clé : user:{userId}
  • Valeur : JSON — { name, cart, recentlyViewed, priceTier }
  • TTL : 3600s (cache 1 heure, puis rafraîchissement depuis l'origine)

Avec cette configuration, chaque lecture se fait en 15-25ms — sans requête vers Postgres à Francfort. Le chemin d'écriture diffère : quand une mutation arrive, envoyez une requête POST à l'API d'origine ; l'origine met à jour la base de données et écrit de façon asynchrone dans le KV. Comme la cohérence du KV est « eventual », les nouvelles données apparaissent sur tous les nœuds edge 100ms après l'écriture.

Alternative Vercel Edge Functions

Vercel Edge s'intègre nativement à Next.js — il fonctionne sur la base de middlewares. Configuration :

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';

export async function middleware(req: NextRequest) {
  const userId = req.cookies.get('userId')?.value;
  
  if (!userId) {
    return NextResponse.redirect(new URL('/login', req.url));
  }

  // Vercel KV (compatible Redis, infrastructure Upstash)
  const userCtx = await fetch(`https://YOUR_KV_ENDPOINT/get/user:${userId}`);
  const data = await userCtx.json();

  // Ajoute le contexte à l'en-tête de requête, passe au handler suivant
  const response = NextResponse.next();
  response.headers.set('X-User-Context', JSON.stringify(data));
  
  return response;
}

export const config = {
  matcher: ['/dashboard/:path*', '/checkout/:path*'],
};

Le cold start chez Vercel Edge est 3-8ms, légèrement plus lent que Cloudflare, mais l'intégration avec la génération ISR (Incremental Static Regeneration) de Next.js est puissante. Vous pouvez générer une page statiquement et l'enrichir à l'edge avec le contexte utilisateur — logique « streaming SSR ». Exemple : le layout principal est du HTML statique, le widget utilisateur est injecté et « hydraté » à l'edge.

Trade-offs : Taille du Bundle, Debugging, Coût

Le runtime edge est limité — l'API Node.js complète n'y fonctionne pas. Chez Cloudflare Workers, les modules Node natifs ne passent pas (ex. fs, child_process) ; même limitation chez Vercel Edge. Réduire les dépendances est obligatoire. Exemple : préférez dayjs à date-fns (2KB vs 70KB), utilisez les méthodes ES6 natives au lieu de lodash.

Limites de taille de bundle :

  • Cloudflare Workers : 1MB (5MB compressé)
  • Vercel Edge : 1MB (middleware)

En production, vous devez rester sous 200KB — chaque KB ajoute 0.5-1ms de latence (parsing + exécution). Tree-shaking et code splitting sont critiques. Si vous utilisez React, preact (3KB) est plus judicieux.

Debugging : Edge supporte console.log, mais les stack traces sont incomplètes. Vous pouvez configurer un environnement de test local avec Cloudflare Wrangler CLI (wrangler dev), ou vercel dev chez Vercel pour simuler le runtime edge. En production, un service de suivi d'erreurs comme Sentry est indispensable — vous envoyez les logs d'erreur par HTTP POST depuis l'isolate edge.

Coût : Les Cloudflare Workers offrent les 100K premières requêtes par jour gratuitement, puis $0,50 par million. Le stockage KV : 1GB gratuit, $0,50 pour 10 millions de lectures. Les Vercel Edge Functions se facturent par plan — le plan Pro inclut 1 million d'exécutions. Pour 10 millions de requêtes par mois, le coût edge est $20-40/mois, tandis qu'une architecture basée sur l'origine coûte $150-200 en frais serveur. À mesure que l'échelle augmente, l'avantage edge s'accroît.

Stratégie KV Store : Write-Through vs Write-Behind

La façon dont vous écrivez dans le KV impacte directement la latence. Deux patterns :

Write-Through (Synchrone) : Quand l'API d'origine reçoit une mutation, écrivez à la fois dans la BD et dans le KV, puis répondez quand les deux sont terminés. Cohérence garantie, mais latence d'écriture 150-250ms (deux hops réseau).

// Gestionnaire API d'origine
app.post('/cart/add', async (req, res) => {
  const { userId, productId } = req.body;
  
  // 1. Écrit dans Postgres
  await db.query('INSERT INTO cart_items ...');
  
  // 2. Met à jour le KV
  const userCtx = await getUserContext(userId);
  userCtx.cart.push(productId);
  await kv.put(`user:${userId}`, JSON.stringify(userCtx));
  
  res.json({ success: true });
});

Write-Behind (Asynchrone) : Écrivez dans la BD, renvoyez la réponse, puis un job de fond met à jour le KV. Latence d'écriture 50-80ms, mais risque de staleness de 100-200ms dans le KV.

app.post('/cart/add', async (req, res) => {
  const { userId, productId } = req.body;
  
  await db.query('INSERT INTO cart_items ...');
  
  // Délègue la mise à jour KV à un job asynchrone
  queueKVUpdate('user', userId);
  
  res.json({ success: true });
});

async function queueKVUpdate(type, id) {
  // Queue Redis ou Cloudflare Durable Objects
  await redis.lpush('kv_updates', JSON.stringify({ type, id }));
}

Pour un e-commerce, le write-behind a du sens pour l'ajout au panier — l'utilisateur n'aperçoit pas 100ms, et au checkout, les données les plus récentes sont vérifiées depuis l'origine. Pour les données critiques comme les changements de prix, privilégiez write-through.

Couche Hybride de Cache : Static + Edge SSR

Au lieu de recourir uniquement à Edge SSR, une architecture hybride static + dynamique est plus efficace. Exemple : sur les projets Headless Commerce de Roibase, nous générons statiquement le squelette de la page d'accueil (en-tête, pied de page, liste générale des catégories) et injectons à l'edge les blocs spécifiques à l'utilisateur (icône panier, nom d'utilisateur, widget de recommandations). Avec cette approche, le taux de cache hits atteint 92%.

Architecture avec Next.js :

// app/page.tsx — Layout statique
export default function HomePage() {
  return (
    <main>
      <Header /> {/* Statique */}
      <HeroSection /> {/* Statique */}
      <UserWidget /> {/* Edge SSR */}
      <ProductGrid /> {/* ISR statique, revalidation 60s */}
    </main>
  );
}

// components/UserWidget.tsx — Composant serveur, runtime edge
export const runtime = 'edge';

export default async function UserWidget() {
  const userId = cookies().get('userId')?.value;
  const userCtx = await fetch(`https://kv.../user:${userId}`);
  const data = await userCtx.json();

  return <div>Bienvenue {data.name}</div>;
}

Avec cette configuration, 80% du HTML est servi statiquement depuis le CDN (TTFB 8-12ms) et 20% est généré à l'edge (latence supplémentaire 30-40ms). Le TTFB total est 40-50ms. Avec une architecture SSR complète basée sur l'origine, la même page prenait 180-220ms.

Améliorations avec Streaming SSR : Grâce au mécanisme Suspense de React 18, vous pouvez renvoyer immédiatement la partie statique et streamer la partie Edge SSR. Le navigateur commence à parser le HTML ; l'utilisateur voit le contenu en 20ms, et le widget personnalisé arrive 30ms plus tard après « hydratation ». La latence perçue descend à 20ms.

Scénario Production : Comment 40ms Ont Été Maintenus

Cas réel : site e-commerce basé sur Shopify Hydrogen, Cloudflare Workers + KV. Latence initiale 210ms (origine à Francfort, utilisateur à Istanbul), objectif : moins de 50ms.

Optimisations appliquées :

  1. Réduction de la structure de données KV : Nous avons réduit le JSON du contexte utilisateur de 2,4KB à 800 octets — uniquement les champs critiques (userId, cart, priceTier). Les produits récemment consultés ont été déplacés vers une clé séparée (user:{id}:recent).
  2. Taille du bundle : Preact au lieu de React (3KB), Intl.DateTimeFormat natif au lieu de date-fns. Le bundle du Worker a été réduit de 180KB à 65KB.
  3. Cache hybride : La page d'accueil est statique (cache CDN 300s), seul le bouton « Ajouter au panier » et les prix sont générés à l'edge. Le taux de cache hits est passé de 88% à 94%.
  4. Sélection du PoP edge : Nous avons activé le « Smart Routing » de Cloudflare — il route vers le PoP avec la latence la plus faible pour l'utilisateur. Les utilisateurs d'Istanbul sont servis depuis le PoP de Sofia (22ms RTT) au lieu de Francfort.

Résultats : TTFB 210ms → 42ms (médiane), LCP 2,1s → 0,9s, INP 180ms → 95ms. Le taux de conversion a augmenté de 2,3% à 2,9% (+26% d'amélioration). Le coût mensuel du serveur d'origine est passé de 340$ à 95$ (coût edge $28/mois).

La montée en puissance d'Edge SSR s'