Retour au cours

frontend / nextjs

Edge Runtime vs Node.js Runtime

Leçon 161 exercice

Explication

Ce que vous allez apprendre

  • Distinguer le Node.js Runtime (complet) de l'Edge Runtime (léger et rapide)
  • Déclarer explicitement le runtime d'une route avec export const runtime
  • Comprendre pourquoi le middleware tourne toujours sur l'Edge Runtime
  • Reconnaître les APIs indisponibles sur l'Edge (fs, drivers TCP classiques)
  • Choisir un driver de base de données compatible Edge (HTTP plutôt que TCP)

Dans quel contexte ?

Une équipe déploie une route app/api/geo/route.ts censée répondre en quelques millisecondes pour déterminer le pays du visiteur avant d'afficher les bons tarifs. Un développeur y importe par réflexe le même ORM PostgreSQL basé sur un driver TCP que partout ailleurs dans l'application — et l'application refuse de builder, avec une erreur incompréhensible pour qui ne connaît pas la distinction entre les deux runtimes de Next.js. Cette leçon explique cette contrainte et comment la contourner proprement.

Une question qu'on n'a pas encore posée

Jusqu'ici, on a dit qu'un composant "s'exécute côté serveur". Mais sur QUEL serveur, et avec QUELLES capacités ? Next.js propose deux environnements d'exécution très différents.

Regardons d'abord l'environnement classique : le Node.js Runtime. Il donne accès au système de fichiers (fs), aux drivers de bases de données avec connexions TCP natives (PostgreSQL, MySQL), et à toutes les librairies npm sans restriction.

Cette puissance a une contrepartie. Démarrer une nouvelle instance (un "cold start") prend plus de temps que dans l'autre environnement.

Voyons justement cet autre environnement : l'Edge Runtime. Il n'implémente qu'un sous-ensemble d'APIs Web standards (fetch, Request, Response, Web Crypto), sans fs ni driver TCP classique.

En échange de ces capacités réduites, il offre un vrai avantage. Il démarre quasi instantanément et peut être déployé physiquement plus près de l'utilisateur final, réduisant la latence réseau.

CritèreNode.js RuntimeEdge Runtime
Démarrage (cold start)Plus lentQuasi instantané
Accès fs, drivers TCPOuiNon
LocalisationUne régionDistribué près de l'utilisateur

Prérequis

Cette leçon suppose que tu es à l'aise avec le middleware vu précédemment : il est l'exemple le plus concret de code qui tourne obligatoirement sur l'Edge Runtime.

C'est donc un compromis assumé : moins de capacités, mais beaucoup plus rapide à froid. Une fois ce compromis compris, il reste une contrainte non négociable à connaître.

Le middleware, vu dans une leçon précédente, tourne TOUJOURS sur l'Edge Runtime. Sans exception possible : c'est une contrainte de conception de Next.js, pas une option à activer.

Mais alors, comment accéder à une base de données classique depuis l'Edge ? Certains fournisseurs (Neon, PlanetScale, Turso) proposent des drivers qui communiquent via HTTP plutôt que via une connexion TCP persistante.

Ces drivers compatibles Edge résolvent le problème. Ils permettent d'avoir à la fois la rapidité de l'Edge ET l'accès aux données.

Bonne pratique

Décide du runtime AVANT d'écrire le code d'une route, en fonction de son besoin réel : runtime = "edge" pour une réponse ultra-rapide sans dépendance Node lourde, runtime = "nodejs" (le défaut) dès qu'un driver TCP classique ou le système de fichiers est nécessaire.

Le piège fréquent à connaître avant de choisir un runtime : importer une librairie Node.js classique (un ORM avec driver TCP) dans une route déclarée runtime = "edge" provoque une erreur au build ou au runtime. Le choix du runtime doit se faire AVANT d'écrire le code, selon les besoins réels de la route.

Commandes & code

Edge Runtime vs Node.js Runtime

ts
// app/api/heavy/route.ts — Node.js runtime (par défaut pour la plupart des routes)
export const runtime = "nodejs";

import fs from "fs/promises"; // accès au système de fichiers : disponible uniquement en Node
import { Pool } from "pg";     // driver TCP natif : nécessite Node

const pool = new Pool({ connectionString: process.env.DATABASE_URL });

export async function GET() {
  const result = await pool.query("SELECT NOW()");
  return Response.json({ time: result.rows[0].now });
}
ts
// app/api/geo/route.ts — Edge runtime : démarrage quasi instantané, déployé près de l'utilisateur
export const runtime = "edge";

export async function GET(request: Request) {
  // API Web standards uniquement (fetch, Request, Response, crypto...)
  // PAS de "fs", PAS de driver TCP natif (pg, mysql2), PAS de la plupart des libs Node
  const geo = (request as any).geo; // selon la plateforme d'hébergement

  return Response.json({
    country: geo?.country ?? "unknown",
    city: geo?.city ?? "unknown",
  });
}
ts
// middleware.ts s'exécute TOUJOURS sur l'Edge Runtime — contrainte non désactivable
import { NextRequest, NextResponse } from "next/server";

export function middleware(request: NextRequest) {
  // OK : Web Crypto API, fetch, URL...
  const id = crypto.randomUUID();

  // PAS OK dans le middleware : import de "fs", d'un ORM avec driver TCP, etc.
  return NextResponse.next({ headers: { "x-request-id": id } });
}
ts
// Driver DB compatible Edge (HTTP au lieu de TCP) — ex. Neon, PlanetScale, Turso
export const runtime = "edge";

import { neon } from "@neondatabase/serverless";

const sql = neon(process.env.DATABASE_URL!);

export async function GET() {
  const rows = await sql`SELECT id, name FROM products LIMIT 10`;
  return Response.json(rows);
}
CritèreNode.js RuntimeEdge Runtime
Démarrage (cold start)Plus lentQuasi instantané
APIs disponiblesComplètes (fs, TCP, natives)Sous-ensemble Web standard
LocalisationUne région (ou plusieurs instances)Distribué près de l'utilisateur
Cas d'usageDB via driver TCP, traitement lourdAuth légère, geo, A/B testing, réponses rapides

Résumé

  • Le runtime se déclare par route via export const runtime = "edge" | "nodejs".
  • Le middleware est TOUJOURS sur l'Edge Runtime : penser compatibilité dès sa conception.
  • Edge = latence minimale mais capacités réduites ; Node.js = capacités complètes mais cold starts plus longs.
  • Pour de la DB sur Edge, utiliser un driver HTTP (Neon, PlanetScale) plutôt qu'un driver TCP classique.

Exercices pratiques

1 disponible
1

Mission : le build qui échoue sur la route geo

Objectif : Diagnostiquer l'incompatibilité entre un driver TCP et l'Edge Runtime, et choisir le runtime adapté à chaque route selon son besoin réel.

Contexte

app/api/geo/route.ts est déclarée export const runtime = "edge" et doit répondre en quelques millisecondes pour déterminer le pays du visiteur. Un développeur y importe import { Pool } from "pg" (le même ORM PostgreSQL basé sur un driver TCP utilisé partout ailleurs) pour logger chaque requête géo en base. Le build échoue avec une erreur incompréhensible pour qui ne connaît pas la distinction entre les deux runtimes.

Résoudre l’exercice →