Edge runtime проти Node.js runtime у Next.js
Edge runtime запускає серверний код Next.js у V8 ізолятах (isolates), розподілених між 200+ CDN-локаціями по всьому світу. Node.js runtime (за замовчуванням) дає повний доступ до екосистеми Node.js: fs, нативні модулі, постійні з'єднання та будь-який npm-пакет.
Теорія
TL;DR
- Node.js - це повноцінна кухня з усіма приладами:
fs,crypto,net, нативні модулі, повна пам'ять - Edge - це вендинговий автомат у кожному аеропорту: обмежені інструменти, але миттєво звідусіль
- Головна різниця: Node.js використовує V8 + event loop libuv; Edge - V8 ізоляти тільки з Web API
- Холодний старт: Edge ~40ms проти ~400ms у Node.js (дані Vercel)
- Правило вибору: Edge для автентифікації та редиректів; Node.js для бази даних і важких обчислень
Швидкий приклад
// Node.js runtime (за замовчуванням) - повний доступ до API
import fs from 'fs';
export async function GET() {
const data = fs.readFileSync('./products.json', 'utf-8');
return Response.json(JSON.parse(data)); // читає з локального диска
}
// Edge runtime - тільки Web API
export const runtime = 'edge';
export async function GET() {
const res = await fetch('https://api.example.com/products');
return Response.json(await res.json()); // ~40ms з найближчої точки присутності
}Node.js читає файл з диска. Edge не може звертатись до файлової системи взагалі, тому ти звертаєшся до зовнішнього API. Компроміс простий: повні можливості проти глобальної затримки.
Ключова різниця
Node.js runtime - це стандартний серверний процес: V8 плюс I/O-цикл libuv. Він відкриває сокети, читає файли, запускає нативні доповнення. Edge runtime компілює код у V8 ізоляти, прибирає все що потребує системних викликів ОС, і розгортає копії в точках присутності (PoPs) CDN по всьому світу. Ти втрачаєш доступ до файлової системи та вбудовані модулі Node.js. Отримуєш глобальне розміщення та холодний старт ~40ms замість ~400ms.
Коли що використовувати
- Перевірка JWT, автентифікація, редиректи -> Edge (без звернення до бази, виконується близько до користувача)
- Гео-маршрутизація, A/B тестування -> Edge (
request.geoдоступний лише в Edge middleware) - Запити до бази через Prisma або Drizzle -> Node.js (нативні драйвери не працюють в ізолятах)
- Обробка файлів, маніпуляція зображеннями -> Node.js (потрібен
fsі нативні прив'язки) - Довгі обчислення (>1с) -> Node.js (Edge обмежений 30с CPU та ~1MB пам'яті)
- Стейтлес-шляхи з важливим холодним стартом -> Edge; пули з'єднань -> Node.js
Таблиця порівняння
| Можливість | Node.js Runtime | Edge Runtime |
|---|---|---|
| API | Повний Node.js (fs, crypto, net) | Web-стандарти (Fetch, URLSearchParams, crypto.subtle) |
| Де виконується | Один регіон або self-hosted | 200+ PoPs глобально (V8 ізоляти) |
| Холодний старт | ~400ms | ~40ms |
| Ліміт CPU | 300с | 30с |
| Пам'ять | Повна купа (heap) | ~1MB |
| Файлова система | Так (модуль fs) | Ні |
| Нативні модулі | Так | Ні |
| npm-пакети | Всі | Тільки сумісні з Web API |
| Стрімінг | Так | Так (до 4MB у Next.js 15) |
| Стан | Постійний (з'єднання перевикористовуються) | Тільки stateless |
| Для чого | База даних, файли, важкі обчислення | Автентифікація, редиректи, персоналізація |
Як це працює зсередини
Next.js компілює Edge-маршрути в бандли V8 ізолятів через esbuild. Жодного I/O-циклу libuv тут немає. Будь-який імпорт вбудованого Node.js модуля призводить до помилки на етапі збірки (next build), а не під час виконання. При деплої на Vercel Edge-бандли запускаються в середовищі, сумісному з Cloudflare Workers, яке доповнює деякі відсутні Web API, але повністю відхиляє fs, path, net та подібні модулі.
Middleware у Next.js завжди виконується на Edge. Це не налаштовується.
Типові помилки
Імпорт fs в Edge-маршруті:
// Помилка збірки: "Module not found: Can't resolve 'fs'"
export const runtime = 'edge';
import fs from 'fs'; // падає на next build
// Виправлення: fetch з CDN або зовнішнього API
const res = await fetch('https://my-cdn.com/data.json');
const data = await res.text();Великі змінні середовища понад 4KB:
Edge серіалізує змінні середовища в заголовки запиту. Секрети понад 4KB обрізаються без жодного попередження в розробці. Баг проявляється лише в продакшені.
// Обрізається в продакшені, помилка не кидається
const bigKey = process.env.HUGE_PRIVATE_KEY;
// Виправлення: Vercel Edge Config або зовнішній API секретів
const config = await fetch('https://edge-config.vercel.com/...');Перенесення Express-обробників напряму:
// Працює в Node.js, повертає 400 в Edge
export default (req, res) => res.json({ body: req.body });
// Виправлення: Edge використовує Web Request API
export async function POST(request: Request) {
const body = await request.json();
return Response.json({ body });
}Несумісність крипто API між рантаймами: Edge використовує crypto.subtle (Web Crypto API). Node.js традиційно використовує crypto.createCipheriv. Якщо написати Edge-код з crypto.subtle і перенести в Node.js обробник, він спрацює на Node.js 18+ (там crypto.subtle є глобальним). У зворотньому напрямку - ніколи: createCipheriv в Edge-маршруті впаде на збірці.
Перевищення ліміту пам'яті 1MB у циклах: V8 ізолят завершує запит при нестачі пам'яті без зрозумілого повідомлення. Якщо обробляєш великі дані, використовуй стрімінг: for await (const chunk of stream) замість буферизації всього в пам'яті.
Де це використовується в реальних проєктах
- Vercel Speed Insights: Edge для гео-вибірки між регіонами
- Stripe і Supabase: Edge middleware для перевірки JWT (без звернення до бази)
- Shopify Hydrogen: Edge-маршрути для персоналізації кошика
- Linear.app: Edge API для розсилки вебхуків
- T3 Stack (
create-t3-app): middleware автентифікації на Edge за замовчуванням
Follow-up питання
Q: Які Node.js API відсутні в Edge runtime?
A: fs, path, net, zlib, child_process та більшість вбудованих модулів Node.js. Замінюй їх на Fetch, URLSearchParams та crypto.subtle.
Q: Як вибір Edge впливає на збірку проєкту?
A: esbuild прибирає Node.js-поліфіли і зупиняє збірку, якщо будь-який імпортований модуль залежить від Node built-in. Проблеми видно при next build, а не в продакшені.
Q: Які реальні числа холодного старту?
A: Бенчмарки Vercel показують Edge ~40ms і Node.js ~400ms. Різниця в ціні запуску ізоляту: майже нульова, проти ініціалізації Node-процесу.
Q: Які обмеження Edge з'явились у Next.js 15?
A: Стрімінг в Edge-маршрутах обмежений 4MB. Функція cache() з React також не працює в Edge-обробниках.
Q (senior): Ти хочеш обмежувати запити за IP через Redis в Edge middleware. У чому проблема і як її вирішити?
A: Edge stateless. Він не може тримати постійне TCP-з'єднання до Redis. Рішення - Upstash Redis, який обертає Redis-команди в HTTP-виклики, доступні через Fetch. На кожен запит платиш один HTTP-roundtrip, тому потрібно враховувати цю затримку і кешувати стан ліміту через TTL-заголовки.
Приклади
Edge проти Node.js для одного маршруту даних
// app/api/node/route.ts - Node.js runtime (за замовчуванням)
import fs from 'fs';
import path from 'path';
export async function GET() {
const filePath = path.join(process.cwd(), 'data', 'products.json');
const raw = fs.readFileSync(filePath, 'utf-8');
return Response.json(JSON.parse(raw));
// Читає з диска, один регіон, ~400ms холодний старт
}// app/api/edge/route.ts - Edge runtime
export const runtime = 'edge';
export async function GET() {
const res = await fetch('https://api.example.com/products');
return Response.json(await res.json());
// Без файлової системи, 200+ PoPs, ~40ms холодний старт
}Один і той самий ендпоінт, різні обмеження. Node.js - коли дані лежать на сервері. Edge - коли джерело даних зовнішнє, а користувачі розподілені по всьому світу.
Production middleware: гео-редирект і блокування ботів
// middleware.ts - завжди запускається на Edge, не налаштовується
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const country = request.geo?.country;
const ua = request.headers.get('user-agent') ?? '';
// Блокуємо ботів зі США ще до origin
if (country === 'US' && ua.toLowerCase().includes('bot')) {
return NextResponse.redirect(new URL('/blocked', request.url));
}
if (country === 'UA') {
return NextResponse.rewrite(new URL('/ua', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!_next|favicon.ico).*)'],
};
// Патерн з vercel/commerce - блокує ботів за <20ms глобальноrequest.geo доступний тільки в Edge. В Node.js API-маршрутах цього поля немає. Middleware виконується до рендеру сторінки, на рівні CDN, не торкаючись origin-сервера взагалі.
Несумісність крипто API при міграції між рантаймами
// app/api/encrypt/route.ts - написано для Edge
export const runtime = 'edge';
export async function POST(request: Request) {
// Web Crypto API - працює в Edge і в Node.js 18+
const key = await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const payload = await request.arrayBuffer();
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
payload
);
return new Response(encrypted);
}
// Якщо хтось перепише це на Node.js зі старим API:
// import { createCipheriv, randomBytes } from 'crypto';
// Той варіант ЗЛАМАЄ Edge на збірці.
// Пиши через crypto.subtle - запуститься скрізь.Пиши шифрування через crypto.subtle. Працює в Edge і в Node.js 18+. Я бачив цю проблему в командах, які копіювали обробники Stripe-вебхуків зі старого Node.js-сервісу в Edge middleware: збірка падала, бо оригінальний код використовував createCipheriv, якого Edge ніколи не підтримував.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.