frontend / nextjs
Edge Runtime vs Node.js Runtime
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ère | Node.js Runtime | Edge Runtime |
|---|---|---|
| Démarrage (cold start) | Plus lent | Quasi instantané |
Accès fs, drivers TCP | Oui | Non |
| Localisation | Une région | Distribué 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
// 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 });
}// 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",
});
}// 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 } });
}// 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ère | Node.js Runtime | Edge Runtime |
|---|---|---|
| Démarrage (cold start) | Plus lent | Quasi instantané |
| APIs disponibles | Complètes (fs, TCP, natives) | Sous-ensemble Web standard |
| Localisation | Une région (ou plusieurs instances) | Distribué près de l'utilisateur |
| Cas d'usage | DB via driver TCP, traitement lourd | Auth légère, geo, A/B testing, réponses rapides |
Résumé
- Le
runtimese déclare par route viaexport 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
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.