Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Edge runtime проти Node.js runtime у Next.js». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Edge runtime** у Next.js запускає серверний код у V8 ізолятах (isolates) в 200+ CDN-локаціях з холодним стартом ~40ms, але без файлової системи та вбудованих модулів Node.js. **Node.js runtime** (за замовчуванням) дає повний доступ до `fs`, нативних модулів та будь-якого npm-пакету, з холодним стартом ~400ms. ```tsx export const runtime = 'edge'; // явне підключення export async function GET() { return Response.json({ ok: true }); // виконується глобально за ~40ms } ``` **Ключове:** Edge для stateless операцій з низькою затримкою - автентифікація, редиректи; Node.js для бази даних і роботи з файлами.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**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 для бази даних і важких обчислень ### Швидкий приклад ```tsx // 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-маршруті:** ```tsx // Помилка збірки: "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 обрізаються без жодного попередження в розробці. Баг проявляється лише в продакшені. ```tsx // Обрізається в продакшені, помилка не кидається const bigKey = process.env.HUGE_PRIVATE_KEY; // Виправлення: Vercel Edge Config або зовнішній API секретів const config = await fetch('https://edge-config.vercel.com/...'); ``` **Перенесення Express-обробників напряму:** ```tsx // Працює в 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 для одного маршруту даних ```tsx // 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 холодний старт } ``` ```tsx // 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: гео-редирект і блокування ботів ```tsx // 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 при міграції між рантаймами ```tsx // 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 ніколи не підтримував.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.