Retour au blog

Edge Functions et l'Avenir du Serverless : Architecture Moderne en 2025

Salut HaWkers, le monde serverless a significativement évolué ces dernières années, et 2025 marque la consolidation des Edge Functions comme standard pour les applications haute performance. Si vous n'êtes pas encore familier avec l'edge computing ou voulez comprendre comment exploiter cette technologie, cet article vous guidera à travers les concepts et pratiques les plus importants.

Que sont exactement les Edge Functions ? Pourquoi offrent-elles une performance supérieure aux fonctions serverless traditionnelles ? Et comment pouvez-vous les implémenter dans vos projets ? Explorons tout cela avec des exemples pratiques.

Que Sont les Edge Functions

Les Edge Functions sont des fonctions serverless qui s'exécutent sur des serveurs distribués géographiquement, proches des utilisateurs finaux. Contrairement aux fonctions traditionnelles qui tournent dans des régions spécifiques, l'edge computing amène le calcul à la bordure du réseau.

Différence Entre Serverless Traditionnel et Edge

Serverless Traditionnel :

  • S'exécute dans des régions spécifiques (us-east-1, eu-west-1, etc.)
  • La latence dépend de la distance de l'utilisateur au datacenter
  • Le cold start peut être significatif (centaines de ms à secondes)
  • Environnement Node.js complet généralement disponible

Edge Functions :

  • S'exécutent sur des centaines de points de présence globaux
  • Latence constamment basse indépendamment de la localisation
  • Cold start minimal (quelques millisecondes)
  • Runtime optimisé basé sur V8 ou similaire

💡 Contexte : Pour un utilisateur en France accédant à une fonction en us-east-1, la latence réseau peut être de 100-200ms. Avec l'edge, cette latence tombe à 10-30ms.

Pourquoi les Edge Functions Dominent en 2025

L'adoption des edge functions a crû de façon exponentielle pour plusieurs raisons :

1. Performance Globale

Avec des serveurs dans plus de 300 emplacements autour du monde, les utilisateurs de n'importe où ont une latence similaire et basse.

2. Rapport Coût-Bénéfice

Malgré un coût par exécution similaire, la latence plus faible signifie moins de temps d'attente et une meilleure expérience, justifiant l'investissement.

3. Cas d'Usage Étendus

Initialement utilisées uniquement pour des redirections simples, elles supportent maintenant :

Cas d'usage modernes :

  • Authentification et autorisation
  • Personnalisation de contenu
  • A/B testing
  • Rate limiting
  • Manipulation de headers
  • Rendu côté serveur
  • APIs complètes

4. Écosystème Mature

Des plateformes comme Cloudflare Workers, Vercel Edge Functions, Deno Deploy et AWS CloudFront Functions ont significativement mûri.

Implémenter des Edge Functions en Pratique

Voyons des exemples pratiques utilisant différentes plateformes.

Vercel Edge Functions

Parfaites pour les projets Next.js :

// pages/api/geo.ts ou app/api/geo/route.ts
import { NextRequest, NextResponse } from 'next/server';

export const config = {
  runtime: 'edge', // Définit que c'est une edge function
};

export default function handler(req: NextRequest) {
  // Informations de géolocalisation disponibles automatiquement
  const country = req.geo?.country ?? 'Inconnu';
  const city = req.geo?.city ?? 'Inconnue';
  const region = req.geo?.region ?? 'Inconnue';

  return NextResponse.json({
    message: `Bonjour visiteur de ${city}, ${region}, ${country} !`,
    latence: 'Cette réponse vient de l\'edge le plus proche de vous',
    timestamp: new Date().toISOString(),
  });
}

Cloudflare Workers

L'une des plateformes les plus populaires pour l'edge :

// worker.ts
export interface Env {
  MY_KV: KVNamespace;
}

export default {
  async fetch(
    request: Request,
    env: Env,
    ctx: ExecutionContext
  ): Promise<Response> {
    const url = new URL(request.url);

    // Rate limiting simple utilisant KV
    const ip = request.headers.get('CF-Connecting-IP') ?? 'unknown';
    const rateLimitKey = `ratelimit:${ip}`;

    const currentCount = await env.MY_KV.get(rateLimitKey);
    const count = currentCount ? parseInt(currentCount) : 0;

    if (count > 100) {
      return new Response('Rate limit exceeded', { status: 429 });
    }

    // Incrémente le compteur avec TTL de 1 minute
    ctx.waitUntil(
      env.MY_KV.put(rateLimitKey, String(count + 1), {
        expirationTtl: 60
      })
    );

    // Logique principale
    if (url.pathname === '/api/data') {
      const data = await fetchDataFromOrigin();
      return new Response(JSON.stringify(data), {
        headers: { 'Content-Type': 'application/json' },
      });
    }

    return new Response('Not Found', { status: 404 });
  },
};

async function fetchDataFromOrigin() {
  // Simule la récupération de données
  return { message: 'Données de l\'edge', timestamp: Date.now() };
}

Deno Deploy

Plateforme native pour l'edge avec Deno :

// server.ts
import { serve } from 'https://deno.land/std/http/server.ts';

interface RequestInfo {
  method: string;
  url: string;
  userAgent: string | null;
  region: string | null;
}

serve(async (request: Request): Promise<Response> => {
  const requestInfo: RequestInfo = {
    method: request.method,
    url: request.url,
    userAgent: request.headers.get('user-agent'),
    region: Deno.env.get('DENO_REGION') ?? 'unknown',
  };

  // Routage simple
  const url = new URL(request.url);

  switch (url.pathname) {
    case '/':
      return new Response('Hello from Deno Edge!', {
        headers: { 'content-type': 'text/plain' },
      });

    case '/api/info':
      return new Response(JSON.stringify(requestInfo), {
        headers: { 'content-type': 'application/json' },
      });

    case '/api/time':
      const now = new Date();
      return new Response(
        JSON.stringify({
          utc: now.toUTCString(),
          iso: now.toISOString(),
          region: requestInfo.region,
        }),
        { headers: { 'content-type': 'application/json' } }
      );

    default:
      return new Response('Not Found', { status: 404 });
  }
});

Patterns Avancés avec les Edge Functions

Middleware d'Authentification

// middleware.ts (Next.js Edge Middleware)
import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify } from 'jose';

const PUBLIC_PATHS = ['/', '/login', '/api/auth'];
const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET);

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // Permet les routes publiques
  if (PUBLIC_PATHS.some(path => pathname.startsWith(path))) {
    return NextResponse.next();
  }

  // Vérifie le token JWT
  const token = request.cookies.get('auth-token')?.value;

  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  try {
    const { payload } = await jwtVerify(token, JWT_SECRET);

    // Ajoute les infos utilisateur aux headers
    const response = NextResponse.next();
    response.headers.set('x-user-id', payload.sub as string);
    response.headers.set('x-user-role', payload.role as string);

    return response;
  } catch (error) {
    // Token invalide
    const response = NextResponse.redirect(new URL('/login', request.url));
    response.cookies.delete('auth-token');
    return response;
  }
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico).*)',
  ],
};

A/B Testing sur l'Edge

// Cloudflare Worker pour A/B testing
export default {
  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url);

    // Vérifie le cookie existant ou attribue une variante
    const cookies = request.headers.get('Cookie') ?? '';
    let variant = getCookie(cookies, 'ab-variant');

    if (!variant) {
      // Attribue une variante aléatoirement (50/50)
      variant = Math.random() < 0.5 ? 'A' : 'B';
    }

    // Modifie la réponse basée sur la variante
    const response = await fetch(request);
    const html = await response.text();

    let modifiedHtml = html;
    if (variant === 'B') {
      // Applique les changements de la variante B
      modifiedHtml = html.replace(
        '<button class="cta">Acheter Maintenant</button>',
        '<button class="cta-new">Ajouter au Panier</button>'
      );
    }

    const newResponse = new Response(modifiedHtml, response);

    // Définit le cookie pour maintenir la cohérence
    newResponse.headers.append(
      'Set-Cookie',
      `ab-variant=${variant}; Path=/; Max-Age=604800`
    );

    return newResponse;
  },
};

function getCookie(cookies: string, name: string): string | null {
  const match = cookies.match(new RegExp(`${name}=([^;]+)`));
  return match ? match[1] : null;
}

Limitations et Considérations

Ce Que les Edge Functions Ne Font Pas Bien

Limitations importantes :

  • Temps d'exécution limité (généralement 30s max, certains 50ms)
  • Mémoire restreinte (128MB typique)
  • Pas d'accès au filesystem
  • APIs limitées (pas de Node.js natif complet)
  • Connexions longue durée problématiques

Quand Utiliser le Serverless Traditionnel

Préférez le serverless traditionnel pour :

  • Traitement intensif CPU
  • Opérations de base de données complexes
  • Tâches de longue durée
  • Quand vous avez besoin de l'écosystème Node.js complet
  • Traitement de gros fichiers

Architecture Hybride

La meilleure approche combine généralement edge et serverless :

Utilisateur

Edge Function (authentification, cache, personnalisation)

Serverless Function (logique métier, base de données)

Base de Données / Services

Comparatif des Plateformes

Plateforme Cold Start Emplacements Langages Usage Gratuit
Cloudflare Workers ~0ms 300+ JS/TS, Rust, WASM 100k req/jour
Vercel Edge ~1ms 100+ JS/TS Selon plan
Deno Deploy ~1ms 35+ JS/TS, WASM 1M req/mois
AWS CloudFront ~5ms 400+ JS Selon usage
Fastly Compute ~5ms 70+ JS, Rust, WASM Selon usage

Choisir la Bonne Plateforme

Cloudflare Workers :

  • Meilleur pour les applications standalone
  • Écosystème le plus mature (KV, D1, R2)
  • Prix compétitifs

Vercel Edge :

  • Idéal pour les projets Next.js
  • Intégration parfaite avec le framework
  • Edge Middleware puissant

Deno Deploy :

  • Excellent pour ceux qui préfèrent Deno
  • Deploy via GitHub simple
  • TypeScript first-class

Meilleures Pratiques

1. Cache Agressif

// Implémentez du cache pour les réponses qui peuvent être cachées
export default {
  async fetch(request: Request): Promise<Response> {
    const cacheKey = new Request(request.url, request);
    const cache = caches.default;

    // Vérifie le cache d'abord
    let response = await cache.match(cacheKey);

    if (!response) {
      response = await fetch(request);

      // Clone pour cacher
      response = new Response(response.body, response);
      response.headers.set('Cache-Control', 'public, max-age=3600');

      // Cache en arrière-plan
      ctx.waitUntil(cache.put(cacheKey, response.clone()));
    }

    return response;
  },
};

2. Gestion des Erreurs Robuste

export default {
  async fetch(request: Request): Promise<Response> {
    try {
      return await handleRequest(request);
    } catch (error) {
      console.error('Edge function error:', error);

      // Retourne une réponse d'erreur conviviale
      return new Response(
        JSON.stringify({
          error: 'Internal Error',
          message: 'Something went wrong',
        }),
        {
          status: 500,
          headers: { 'Content-Type': 'application/json' },
        }
      );
    }
  },
};

3. Monitoring et Observabilité

// Ajoutez du logging structuré
interface LogEntry {
  timestamp: string;
  level: 'info' | 'warn' | 'error';
  message: string;
  metadata?: Record<string, unknown>;
}

function log(entry: LogEntry) {
  console.log(JSON.stringify(entry));
}

export default {
  async fetch(request: Request): Promise<Response> {
    const startTime = Date.now();

    const response = await handleRequest(request);

    log({
      timestamp: new Date().toISOString(),
      level: 'info',
      message: 'Request handled',
      metadata: {
        url: request.url,
        method: request.method,
        status: response.status,
        duration: Date.now() - startTime,
      },
    });

    return response;
  },
};

Conclusion

Les Edge Functions représentent une évolution significative dans l'architecture serverless, offrant une latence constamment basse pour les utilisateurs globaux. En 2025, elles sont devenues un composant essentiel pour les applications qui priorisent la performance.

La clé est de comprendre quand utiliser l'edge versus le serverless traditionnel. Pour l'authentification, la personnalisation, le rate limiting et les réponses rapides, l'edge est idéal. Pour le traitement lourd et la logique métier complexe, le serverless traditionnel reste le bon choix.

La combinaison des deux offre le meilleur des deux mondes : des réponses rapides sur l'edge avec la puissance de traitement du backend quand nécessaire.

Si vous voulez approfondir les architectures modernes, je recommande de jeter un œil à un autre article : PWAs avec JavaScript : La Révolution des Applications Web où vous découvrirez comment construire des expériences web modernes et performantes.

C'est parti ! 🦅

Commentaires (0)

Cet article n'a pas encore de commentaires. Soyez le premier!

Ajouter des commentaires