Edge Functions: Co to je, kdy je použít a kdy ne
Edge runtime, V8 isoláty, mýtus o cold startu, geo-routing, A/B testování, autentizace na edge a proč jsem některé věci přesunul zpět na Node.js. Vyvážený pohled na edge computing.
Slovo "edge" se používá hodně. Vercel ho říká. Cloudflare ho říká. Deno ho říká. Každá konferenční přednáška o webovém výkonu nevyhnutelně zmíní "běh na edge" jako magické zaklínadlo, které udělá vaši aplikaci rychlou.
Uvěřil jsem tomu. Přesunul jsem middleware, API routy, dokonce i nějakou renderovací logiku na edge runtime. Některé z těchto kroků byly brilantní. Ostatní jsem potichu přesunul zpět na Node.js o tři týdny později po debugování chyb connection poolu ve 2 ráno.
Tento příspěvek je vyvážená verze toho příběhu — co edge vlastně je, kde opravdu vyniká, kde absolutně nevyniká a jak se rozhoduji, který runtime použít pro každou část své aplikace.
Co je Edge?#
Začněme geografií. Když někdo navštíví vaši webovou stránku, jeho požadavek cestuje z jeho zařízení, přes jeho ISP, přes internet k vašemu serveru, zpracuje se a odpověď cestuje celou cestu zpět. Pokud je váš server v us-east-1 (Virginia) a váš uživatel je v Tokiu, tato cesta tam a zpět pokrývá zhruba 14 000 km. Rychlostí světla přes optické vlákno to je asi 70ms jen pro fyziku — jedním směrem. Přidejte DNS rozlišení, TLS handshake a jakýkoliv čas zpracování a snadno se díváte na 200-400ms, než váš uživatel uvidí jediný bajt.
"Edge" znamená běh vašeho kódu na serverech distribuovaných globálně — stejné CDN nody, které vždy obsluhovaly statické assety, ale nyní mohou také vykonávat vaši logiku. Místo jednoho origin serveru ve Virginii váš kód běží na 300+ lokacích po celém světě. Uživatel v Tokiu narazí na server v Tokiu. Uživatel v Paříži narazí na server v Paříži.
Matematika latence je jednoduchá a přesvědčivá:
Tradiční (jeden origin):
Tokio → Virginia: ~140ms round trip (jen fyzika)
+ TLS handshake: ~140ms navíc (další round trip)
+ Zpracování: 20-50ms
Celkem: ~300-330ms
Edge (lokální PoP):
Tokio → Edge node v Tokiu: ~5ms round trip
+ TLS handshake: ~5ms navíc
+ Zpracování: 5-20ms
Celkem: ~15-30ms
To je 10-20x zlepšení pro úvodní odpověď. Je to reálné, je to měřitelné a pro určité operace je to transformativní.
Ale zde je to, co marketing zamlčuje: edge není plné serverové prostředí. Je to něco zásadně odlišného.
V8 Isoláty vs Node.js#
Tradiční Node.js běží v plném procesu operačního systému. Má přístup k filesystému, může otevírat TCP spojení, může spouštět podřízené procesy, může číst proměnné prostředí jako stream, může v podstatě dělat cokoliv, co může Linux proces.
Edge funkce neběží na Node.js. Běží na V8 isolátech — stejný JavaScript engine, který pohání Chrome, ale ořezaný na své jádro. Představte si V8 isolát jako lehký sandbox:
// Toto funguje v Node.js, ale NE na edge
import fs from "fs";
import { createConnection } from "net";
import { execSync } from "child_process";
const file = fs.readFileSync("/etc/hosts"); // ❌ Žádný filesystem
const conn = createConnection({ port: 5432 }); // ❌ Žádné raw TCP
const result = execSync("ls -la"); // ❌ Žádné podřízené procesy
process.env.DATABASE_URL; // ⚠️ Dostupné, ale statické, nastavené při deployiCo na edge MÁTE k dispozici, je Web API povrch — stejná API dostupná v prohlížeči:
// Toto vše funguje na edge
const response = await fetch("https://api.example.com/data");
const url = new URL(request.url);
const headers = new Headers({ "Content-Type": "application/json" });
const encoder = new TextEncoder();
const encoded = encoder.encode("hello");
const hash = await crypto.subtle.digest("SHA-256", encoded);
const id = crypto.randomUUID();
// Web Streams API
const stream = new ReadableStream({
start(controller) {
controller.enqueue("chunk 1");
controller.enqueue("chunk 2");
controller.close();
},
});
// Cache API
const cache = caches.default;
await cache.put(request, response.clone());Omezení jsou reálná a tvrdá:
- Paměť: 128MB na isolát (Cloudflare Workers), 256MB na některých platformách
- CPU čas: 10-50ms skutečného CPU času (ne wall clock čas —
await fetch()se nepočítá, aleJSON.parse()na 5MB payloadu ano) - Žádné nativní moduly: Cokoliv, co potřebuje C++ binding (bcrypt, sharp, canvas), je out
- Žádná perzistentní spojení: Nemůžete udržet otevřené databázové spojení mezi požadavky
- Limity velikosti bundlu: Typicky 1-5MB pro celý worker skript
Toto není Node.js na CDN. Je to jiný runtime s jiným mentálním modelem.
Cold Starty: Mýtus a realita#
Pravděpodobně jste slyšeli, že edge funkce mají "nulové cold starty". To je... většinou pravda a srovnání je opravdu dramatické.
Tradiční kontejnerová serverless funkce (AWS Lambda, Google Cloud Functions) funguje takto:
- Požadavek dorazí
- Platforma provisionuje kontejner (pokud žádný není k dispozici)
- Kontejner bootuje OS
- Runtime se inicializuje (Node.js, Python, atd.)
- Váš kód se načte a inicializuje
- Požadavek se zpracuje
Kroky 2-5 jsou cold start. Pro Node.js Lambda to je typicky 200-500ms. Pro Java Lambda to může být 2-5 sekund. Pro .NET Lambda 500ms-1,5s.
V8 isoláty fungují jinak:
- Požadavek dorazí
- Platforma vytvoří nový V8 isolát (nebo znovu použije warm)
- Váš kód se načte (je již zkompilován do bytekódu při deployi)
- Požadavek se zpracuje
Kroky 2-3 trvají méně než 5ms. Často méně než 1ms. Isolát není kontejner — není tu žádný OS k bootování, žádný runtime k inicializaci. V8 vytvoří čerstvý isolát v mikrosekundách. Fráze "nulový cold start" je marketingový jazyk, ale realita (pod 5ms startup) je dostatečně blízko nule, že na tom u většiny use casů nezáleží.
Ale zde je, kdy cold starty na edge stále kousají:
Velké bundly. Pokud vaše edge funkce stahuje 2MB závislostí, ten kód stále musí být načten a zparsován. Naučil jsem se to tvrdým způsobem, když jsem zabundloval validační knihovnu a knihovnu pro formátování dat do edge middleware. Cold start šel z 2ms na 40ms. Stále rychlé, ale ne "nulové".
Vzácné lokace. Edge poskytovatelé mají stovky PoP, ale ne všechny PoP udržují váš kód warm. Pokud dostáváte jeden požadavek za hodinu z Nairobi, ten isolát se mezi požadavky recykluje. Další požadavek platí náklady na startup znovu.
Více isolátů na požadavek. Pokud vaše edge funkce volá další edge funkci (nebo pokud middleware i API routa jsou obě edge), můžete roztáčet více isolátů pro jeden uživatelský požadavek.
Praktická rada: udržujte bundly vašich edge funkcí malé. Importujte pouze to, co potřebujete. Agresivně tree-shakujte. Čím menší bundle, tím rychlejší cold start, tím více slib "nulového cold startu" drží.
// ❌ Nedělejte toto na edge
import dayjs from "dayjs";
import * as yup from "yup";
import lodash from "lodash";
// ✅ Dělejte místo toho toto — použijte vestavěná API
const date = new Date().toISOString();
const isValid = typeof input === "string" && input.length < 200;
const unique = [...new Set(items)];Perfektní use casy pro Edge Functions#
Po rozsáhlém experimentování jsem našel jasný pattern: edge funkce vynikají, když potřebujete udělat rychlé rozhodnutí o požadavku předtím, než dorazí na váš origin server. Jsou to strážní, routery a transformátory — ne aplikační servery.
1. Přesměrování na základě geolokace#
Toto je killer use case. Požadavek dorazí na nejbližší edge node, který již ví, kde uživatel je. Není potřeba žádné API volání, žádná IP lookup databáze — platforma poskytuje geo data:
// middleware.ts — běží na edge při každém požadavku
import { NextRequest, NextResponse } from "next/server";
export const config = {
matcher: ["/", "/shop/:path*"],
};
export function middleware(request: NextRequest) {
const country = request.geo?.country ?? "US";
const city = request.geo?.city ?? "Unknown";
const region = request.geo?.region ?? "Unknown";
// Přesměrování na obchod specifický pro zemi
if (request.nextUrl.pathname === "/shop") {
const storeMap: Record<string, string> = {
DE: "/shop/eu",
FR: "/shop/eu",
GB: "/shop/uk",
JP: "/shop/jp",
TR: "/shop/tr",
};
const storePath = storeMap[country] ?? "/shop/us";
if (request.nextUrl.pathname !== storePath) {
return NextResponse.redirect(new URL(storePath, request.url));
}
}
// Přidejte geo hlavičky pro následné použití
const response = NextResponse.next();
response.headers.set("x-user-country", country);
response.headers.set("x-user-city", city);
response.headers.set("x-user-region", region);
return response;
}Toto běží za méně než 5ms, přímo vedle uživatele. Alternativa — odeslání požadavku celou cestu na váš origin server jen pro IP lookup a přesměrování zpět — by stála 100-300ms pro uživatele daleko od vašeho originu.
2. A/B testování bez blikání na klientovi#
Klientské A/B testování způsobuje obávaný "flash of original content" — uživatel vidí verzi A na zlomek sekundy, než JavaScript přepne na verzi B. Na edge můžete přiřadit variantu předtím, než se stránka vůbec začne renderovat:
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
// Zkontrolujte, zda uživatel již má přiřazenou variantu
const existingVariant = request.cookies.get("ab-variant")?.value;
if (existingVariant) {
// Přepište na správnou stránku varianty
const url = request.nextUrl.clone();
url.pathname = `/variants/${existingVariant}${url.pathname}`;
return NextResponse.rewrite(url);
}
// Přiřaďte novou variantu (50/50 split)
const variant = Math.random() < 0.5 ? "control" : "treatment";
const url = request.nextUrl.clone();
url.pathname = `/variants/${variant}${url.pathname}`;
const response = NextResponse.rewrite(url);
response.cookies.set("ab-variant", variant, {
maxAge: 60 * 60 * 24 * 30, // 30 dní
httpOnly: true,
sameSite: "lax",
});
return response;
}Uživatel nikdy nevidí blikání, protože přepsání probíhá na síťové úrovni. Prohlížeč ani neví, že to byl A/B test — prostě obdrží stránku varianty přímo.
3. Ověření Auth tokenu#
Pokud vaše autentizace používá JWT (a neděláte databázové lookup sessions), edge je perfektní. Ověření JWT je čistá kryptografie — žádná databáze není potřeba:
import { jwtVerify, importSPKI } from "jose";
import { NextRequest, NextResponse } from "next/server";
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----`;
export async function middleware(request: NextRequest) {
const token = request.cookies.get("session-token")?.value;
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
try {
const key = await importSPKI(PUBLIC_KEY, "RS256");
const { payload } = await jwtVerify(token, key, {
algorithms: ["RS256"],
issuer: "https://auth.example.com",
});
// Předejte informace o uživateli dále jako hlavičky
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 {
// Token vypršel nebo je neplatný
const response = NextResponse.redirect(new URL("/login", request.url));
response.cookies.delete("session-token");
return response;
}
}Tento pattern je mocný: edge middleware ověří token a předá informace o uživateli vašemu originu jako důvěryhodné hlavičky. Vaše API routy nemusí token ověřovat znovu — prostě přečtou request.headers.get("x-user-id").
4. Detekce botů a Rate Limiting#
Edge funkce mohou blokovat nežádoucí provoz předtím, než vůbec dorazí na váš origin:
import { NextRequest, NextResponse } from "next/server";
// Jednoduchý in-memory rate limiter (per edge lokace)
const rateLimitMap = new Map<string, { count: number; timestamp: number }>();
export function middleware(request: NextRequest) {
const ip = request.headers.get("x-forwarded-for")?.split(",").pop()?.trim()
?? "unknown";
const ua = request.headers.get("user-agent") ?? "";
// Blokování známých špatných botů
const badBots = ["AhrefsBot", "SemrushBot", "MJ12bot", "DotBot"];
if (badBots.some((bot) => ua.includes(bot))) {
return new NextResponse("Forbidden", { status: 403 });
}
// Jednoduchý rate limiting
const now = Date.now();
const windowMs = 60_000; // 1 minuta
const maxRequests = 100;
const entry = rateLimitMap.get(ip);
if (entry && now - entry.timestamp < windowMs) {
entry.count++;
if (entry.count > maxRequests) {
return new NextResponse("Too Many Requests", {
status: 429,
headers: { "Retry-After": "60" },
});
}
} else {
rateLimitMap.set(ip, { count: 1, timestamp: now });
}
// Periodický cleanup pro prevenci memory leaku
if (rateLimitMap.size > 10_000) {
const cutoff = now - windowMs;
for (const [key, val] of rateLimitMap) {
if (val.timestamp < cutoff) rateLimitMap.delete(key);
}
}
return NextResponse.next();
}Jedno upozornění: rate limit mapa výše je per-isolát, per-lokace. Pokud máte 300 edge lokací, každá má svou vlastní mapu. Pro striktní rate limiting potřebujete distribuované úložiště jako Upstash Redis nebo Cloudflare Durable Objects. Ale pro hrubou prevenci zneužití fungují per-lokační limity překvapivě dobře.
5. Přepisování požadavků a personalizační hlavičky#
Edge funkce jsou vynikající v transformaci požadavků předtím, než dorazí na váš origin:
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
const response = NextResponse.next();
const url = request.nextUrl;
// Content negotiation na základě zařízení
const ua = request.headers.get("user-agent") ?? "";
const isMobile = /mobile|android|iphone/i.test(ua);
response.headers.set("x-device-type", isMobile ? "mobile" : "desktop");
// Feature flagy z cookie
const flags = request.cookies.get("feature-flags")?.value;
if (flags) {
response.headers.set("x-feature-flags", flags);
}
// Detekce locale pro i18n
const acceptLanguage = request.headers.get("accept-language") ?? "en";
const preferredLocale = acceptLanguage.split(",")[0]?.split("-")[0] ?? "en";
const supportedLocales = [
"en", "tr", "de", "fr", "es", "pt", "ja", "ko", "it",
"nl", "ru", "pl", "uk", "sv", "cs", "ar", "hi", "zh",
];
const locale = supportedLocales.includes(preferredLocale)
? preferredLocale
: "en";
if (!url.pathname.startsWith(`/${locale}`) && !url.pathname.startsWith("/api")) {
return NextResponse.redirect(new URL(`/${locale}${url.pathname}`, request.url));
}
return response;
}Kde Edge selhává#
Toto je sekce, kterou marketingové stránky přeskakují. Narazil jsem na každou z těchto zdí.
1. Databázová spojení#
Toto je ten velký problém. Tradiční databáze (PostgreSQL, MySQL) používají perzistentní TCP spojení. Node.js server otevře connection pool při startu a znovu používá tato spojení napříč požadavky. Efektivní, ověřené, dobře srozumitelné.
Edge funkce toto nemohou dělat. Každý isolát je efemérní. Neexistuje žádná "startovací" fáze, kde byste otevřeli spojení. I kdybyste mohli otevřít spojení, isolát může být recyklován po jednom požadavku, plýtvající časem na nastavení spojení.
// ❌ Tento pattern na edge fundamentálně nefunguje
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10, // Connection pool o 10
});
// Každé edge vyvolání by:
// 1. Vytvořilo nový Pool (nelze spolehlivě znovu použít napříč vyvoláními)
// 2. Otevřelo TCP spojení k databázi (která je v us-east-1, ne na edge)
// 3. Provedlo TLS handshake s databází
// 4. Spustilo dotaz
// 5. Zahodilo spojení, když se isolát recykluje
// I s connection pooling službami jako PgBouncer
// stále platíte síťovou latenci od edge → origin databázeProblém round-tripu k databázi je fundamentální. Vaše databáze je v jednom regionu. Vaše edge funkce je ve 300 regionech. Každý databázový dotaz z edge musí cestovat z edge lokace do regionu databáze a zpět. Pro uživatele v Tokiu, který narazí na edge node v Tokiu, ale vaše databáze je ve Virginii:
Edge funkce v Tokiu
→ Dotaz na PostgreSQL ve Virginii: ~140ms round trip
→ Druhý dotaz: ~140ms navíc
→ Celkem: 280ms jen pro dva dotazy
Node.js funkce ve Virginii (stejný region jako DB)
→ Dotaz na PostgreSQL: ~1ms round trip
→ Druhý dotaz: ~1ms navíc
→ Celkem: 2ms pro dva dotazy
Edge funkce je 140x pomalejší pro databázové operace v tomto scénáři. Nezáleží na tom, že edge funkce startovala rychleji — round-tripy k databázi dominují všechno.
Proto existují HTTP databázové proxy (serverless driver od Neon, fetch-based driver od PlanetScale, REST API od Supabase). Fungují, ale stále dělají HTTP požadavky na databázi v jednom regionu. Řeší problém "nelze použít TCP", ale ne problém "databáze je daleko".
// ✅ Toto na edge funguje (HTTP přístup k databázi)
// Ale stále je to pomalé, pokud je databáze daleko od edge nodu
import { neon } from "@neondatabase/serverless";
export const runtime = "edge";
export async function GET(request: Request) {
const sql = neon(process.env.DATABASE_URL!);
// Toto provede HTTP požadavek na vaši Neon databázi
// Funguje, ale latence závisí na vzdálenosti k regionu databáze
const posts = await sql`SELECT * FROM posts WHERE published = true LIMIT 10`;
return Response.json(posts);
}2. Dlouho běžící úlohy#
Edge funkce mají limity CPU času, typicky 10-50ms skutečného výpočetního času. Wall clock čas je velkorysejší (obvykle 30 sekund), ale CPU-intenzivní operace narazí na limit rychle:
// ❌ Toto překročí CPU časové limity na edge
export const runtime = "edge";
export async function POST(request: Request) {
const data = await request.json();
// Zpracování obrázků — CPU intenzivní
// (Také nelze použít sharp, protože je to nativní modul)
const processed = heavyImageProcessing(data.image);
// Generování PDF — CPU intenzivní + potřebuje Node.js API
const pdf = generatePDF(data.content);
// Velká transformace dat
const result = data.items // 100 000 položek
.map(transform)
.filter(validate)
.sort(compare)
.reduce(aggregate, {});
return Response.json(result);
}Pokud vaše funkce potřebuje více než pár milisekund CPU času, patří na regionální Node.js server. Tečka.
3. Závislosti pouze pro Node.js#
Tohle lidi zaskočí. Překvapivě mnoho npm balíčků závisí na vestavěných modulech Node.js:
// ❌ Tyto balíčky na edge nebudou fungovat
import bcrypt from "bcrypt"; // Nativní C++ binding
import sharp from "sharp"; // Nativní C++ binding
import puppeteer from "puppeteer"; // Potřebuje filesystem + child_process
import nodemailer from "nodemailer"; // Potřebuje net modul
import { readFile } from "fs/promises"; // Node.js filesystem API
import mongoose from "mongoose"; // TCP spojení + Node.js API
// ✅ Edge-kompatibilní alternativy
import { hashSync } from "bcryptjs"; // Čistá JS implementace (pomalejší)
// Pro obrázky: použijte separátní službu nebo API
// Pro email: použijte HTTP emailové API (Resend, SendGrid REST)
// Pro databázi: použijte HTTP klientyPřed přesunem čehokoliv na edge zkontrolujte každou závislost. Jedno require("fs") zakopané tři úrovně hluboko ve vašem stromu závislostí crashne vaši edge funkci za běhu — ne při buildu. Nasadíte, vše vypadá v pořádku, pak první požadavek narazí na tu kódovou cestu a dostanete kryptickou chybu.
4. Velké velikosti bundlů#
Edge platformy mají přísné limity velikosti bundlů:
- Cloudflare Workers: 1MB (zdarma), 5MB (placené)
- Vercel Edge Functions: 4MB (komprimované)
- Deno Deploy: 20MB
Tohle zní jako dostatek, dokud neimportujete UI komponentní knihovnu, validační knihovnu a datovou knihovnu. Jednou jsem měl edge middleware, který narostl na 3,5MB, protože jsem importoval z barrel souboru, který stáhl celý adresář @/components.
// ❌ Barrel file importy mohou stáhnout příliš mnoho
import { validateEmail } from "@/lib/utils";
// Pokud utils.ts re-exportuje z 20 dalších modulů, všechny se zabundlují
// ✅ Importujte přímo ze zdroje
import { validateEmail } from "@/lib/validators/email";5. Streaming a WebSockety#
Edge funkce mohou dělat streaming odpovědí (Web Streams API), ale dlouhodobá WebSocket spojení jsou jiný příběh. Zatímco některé platformy podporují WebSockety na edge (Cloudflare Workers, Deno Deploy), efemérní povaha edge funkcí je činí špatnou volbou pro stavová, dlouhodobá spojení.
Next.js Edge Runtime#
Next.js umožňuje přehledně přejít na edge runtime na bázi per-route. Nemusíte jít all-in — přesně si vyberete, které routy běží na edge.
Middleware (vždy Edge)#
Next.js middleware vždy běží na edge. To je záměr — middleware zachytává každý odpovídající požadavek, takže musí být rychlý a globálně distribuovaný:
// middleware.ts — vždy běží na edge, žádný opt-in potřeba
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
// Toto běží před každým odpovídajícím požadavkem
// Udržujte to rychlé — žádná databázová volání, žádné těžké výpočty
return NextResponse.next();
}
export const config = {
// Běží pouze na konkrétních cestách
matcher: [
"/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)",
],
};API routy na Edge#
Jakýkoliv route handler může optovat do edge runtime:
// app/api/hello/route.ts
export const runtime = "edge"; // Tento jeden řádek změní runtime
export async function GET(request: Request) {
return Response.json({
message: "Hello from the edge",
region: process.env.VERCEL_REGION ?? "unknown",
timestamp: Date.now(),
});
}Stránkové routy na Edge#
Dokonce i celé stránky se mohou renderovat na edge, i když bych nad tím pečlivě přemýšlel:
// app/dashboard/page.tsx
export const runtime = "edge";
export default async function DashboardPage() {
// Pamatujte: žádná Node.js API zde
// Jakékoliv načítání dat musí používat fetch() nebo edge-kompatibilní klienty
const data = await fetch("https://api.example.com/dashboard", {
headers: { Authorization: `Bearer ${process.env.API_KEY}` },
next: { revalidate: 60 },
}).then((r) => r.json());
return (
<main>
<h1>Dashboard</h1>
{/* renderování dat */}
</main>
);
}Co je dostupné v Edge Runtime#
Zde je praktická reference toho, co můžete a nemůžete použít:
// ✅ Dostupné na edge
fetch() // HTTP požadavky
Request / Response // Web standard request/response
Headers // HTTP hlavičky
URL / URLSearchParams // Parsování URL
TextEncoder / TextDecoder // Kódování řetězců
crypto.subtle // Krypto operace (podepisování, hashování)
crypto.randomUUID() // Generování UUID
crypto.getRandomValues() // Kryptografická náhodná čísla
structuredClone() // Hluboké klonování
atob() / btoa() // Base64 kódování/dekódování
setTimeout() / setInterval() // Časovače (ale pamatujte na CPU limity)
console.log() // Logování
ReadableStream / WritableStream // Streaming
AbortController / AbortSignal // Zrušení požadavku
URLPattern // Párování URL vzorů
// ❌ NEDOSTUPNÉ na edge
require() // CommonJS (použijte import)
fs / path / os // Vestavěné moduly Node.js
process.exit() // Řízení procesu
Buffer // Použijte Uint8Array místo toho
__dirname / __filename // Použijte import.meta.url
setImmediate() // Není webový standardAuth na Edge: Kompletní pattern#
Chci jít hlouběji do autentizace, protože je to jeden z nejdůležitějších edge use casů, ale je také snadné to pokazit.
Pattern, který funguje: ověřte token na edge, předejte důvěryhodné claims dále, nikdy se nedotýkejte databáze v middleware.
// lib/edge-auth.ts — Edge-kompatibilní auth utility
import { jwtVerify, SignJWT, importSPKI, importPKCS8 } from "jose";
const PUBLIC_KEY_PEM = process.env.JWT_PUBLIC_KEY!;
const ISSUER = "https://auth.myapp.com";
const AUDIENCE = "https://myapp.com";
export interface TokenPayload {
sub: string;
email: string;
role: "user" | "admin" | "moderator";
iat: number;
exp: number;
}
export async function verifyToken(token: string): Promise<TokenPayload | null> {
try {
const publicKey = await importSPKI(PUBLIC_KEY_PEM, "RS256");
const { payload } = await jwtVerify(token, publicKey, {
algorithms: ["RS256"],
issuer: ISSUER,
audience: AUDIENCE,
clockTolerance: 30, // 30 sekund tolerance hodinového posunu
});
return payload as unknown as TokenPayload;
} catch {
return null;
}
}
export function isTokenExpiringSoon(payload: TokenPayload): boolean {
const now = Math.floor(Date.now() / 1000);
const fiveMinutes = 5 * 60;
return payload.exp - now < fiveMinutes;
}// middleware.ts — Auth middleware
import { NextRequest, NextResponse } from "next/server";
import { verifyToken, isTokenExpiringSoon } from "./lib/edge-auth";
const PUBLIC_PATHS = ["/", "/login", "/register", "/api/auth/login"];
const ADMIN_PATHS = ["/admin"];
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Přeskočte auth pro veřejné cesty
if (PUBLIC_PATHS.some((p) => pathname === p || pathname.startsWith("/api/public"))) {
return NextResponse.next();
}
// Extrahujte token
const token = request.cookies.get("auth-token")?.value;
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
// Ověřte token (čistá kryptografie — žádné databázové volání)
const payload = await verifyToken(token);
if (!payload) {
const response = NextResponse.redirect(new URL("/login", request.url));
response.cookies.delete("auth-token");
return response;
}
// Řízení přístupu na základě rolí
if (ADMIN_PATHS.some((p) => pathname.startsWith(p)) && payload.role !== "admin") {
return NextResponse.redirect(new URL("/unauthorized", request.url));
}
// Předejte ověřené informace o uživateli originu jako důvěryhodné hlavičky
const response = NextResponse.next();
response.headers.set("x-user-id", payload.sub);
response.headers.set("x-user-email", payload.email);
response.headers.set("x-user-role", payload.role);
// Signalizujte, pokud token potřebuje refresh
if (isTokenExpiringSoon(payload)) {
response.headers.set("x-token-refresh", "true");
}
return response;
}// app/api/profile/route.ts — Origin server čte důvěryhodné hlavičky
export async function GET(request: Request) {
// Tyto hlavičky nastavil edge middleware po ověření JWT
// Jsou důvěryhodné, protože pocházejí z naší vlastní infrastruktury
const userId = request.headers.get("x-user-id");
const userRole = request.headers.get("x-user-role");
if (!userId) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
// Nyní můžeme zavolat databázi — jsme na origin serveru,
// přímo vedle databáze, s connection poolem
const user = await db.user.findUnique({ where: { id: userId } });
return Response.json(user);
}Klíčový vhled: edge dělá rychlou část (kryptografické ověření) a origin dělá pomalou část (databázové dotazy). Každý běží tam, kde je nejefektivnější.
Jedno důležité upozornění: toto funguje pouze pro JWT. Pokud váš auth systém vyžaduje databázový lookup při každém požadavku (jako session-based auth s session ID cookie), edge nemůže pomoci — stále byste museli volat databázi, což znamená round-trip do origin regionu.
Edge Caching#
Cachování na edge je místo, kde se věci stávají zajímavými. Edge nody mohou cachovat odpovědi, což znamená, že následné požadavky na stejnou URL jsou obslouženy přímo z edge bez zásahu vašeho originu.
Cache-Control správně#
// app/api/products/route.ts
export const runtime = "edge";
export async function GET(request: Request) {
const url = new URL(request.url);
const category = url.searchParams.get("category") ?? "all";
const products = await fetch(
`${process.env.ORIGIN_API}/products?category=${category}`,
).then((r) => r.json());
return Response.json(products, {
headers: {
// Cache na CDN po dobu 60 sekund
// Servírujte zastaralé během revalidace až 5 minut
// Klient může cachovat 10 sekund
"Cache-Control": "public, s-maxage=60, stale-while-revalidate=300, max-age=10",
// Variujte podle těchto hlaviček, aby různé varianty měly různé cache záznamy
Vary: "Accept-Language, Accept-Encoding",
// CDN-specifický cache tag pro cílené invalidace
"Cache-Tag": `products,category-${category}`,
},
});
}Pattern stale-while-revalidate je obzvláště mocný na edge. Zde je, co se stane:
- První požadavek: Edge načte z originu, cachuje odpověď, vrátí ji
- Požadavky do 60 sekund: Edge servíruje z cache (0ms origin latence)
- Požadavek v 61-360 sekundách: Edge servíruje zastaralou cachovanou verzi okamžitě, ale načítá čerstvou verzi z originu na pozadí
- Po 360 sekundách: Cache je plně vypršená, další požadavek jde na origin
Vaši uživatelé téměř vždy dostanou cachovanou odpověď. Kompromis čerstvosti je explicitní a laditelný.
Edge Config pro dynamickou konfiguraci#
Vercel Edge Config (a podobné služby od jiných platforem) umožňuje ukládat key-value konfiguraci, která je replikována na každou edge lokaci. To je neuvěřitelně užitečné pro feature flagy, pravidla přesměrování a konfiguraci A/B testů, které chcete aktualizovat bez redeploye:
import { get } from "@vercel/edge-config";
import { NextRequest, NextResponse } from "next/server";
export async function middleware(request: NextRequest) {
// Čtení Edge Config je extrémně rychlé (~1ms), protože
// data jsou replikována na každou edge lokaci
const maintenanceMode = await get<boolean>("maintenance_mode");
if (maintenanceMode) {
return NextResponse.rewrite(new URL("/maintenance", request.url));
}
// Feature flagy
const features = await get<Record<string, boolean>>("feature_flags");
if (features?.["new_pricing_page"] && request.nextUrl.pathname === "/pricing") {
return NextResponse.rewrite(new URL("/pricing-v2", request.url));
}
// Dynamická přesměrování (aktualizace přesměrování bez redeploye)
const redirects = await get<Array<{ from: string; to: string; permanent: boolean }>>(
"redirects",
);
if (redirects) {
const match = redirects.find((r) => r.from === request.nextUrl.pathname);
if (match) {
return NextResponse.redirect(
new URL(match.to, request.url),
match.permanent ? 308 : 307,
);
}
}
return NextResponse.next();
}Toto je opravdový game-changer. Před Edge Config změna feature flagu znamenala změnu kódu a redeploy. Nyní aktualizujete JSON hodnotu v dashboardu a šíří se globálně během sekund.
Reálná matematika výkonu#
Pojďme udělat poctivou matematiku místo marketingové. Porovnám tři architektury pro typický API endpoint, který potřebuje dotazovat databázi:
Scénář: User Profile API (2 databázové dotazy)#
Architektura A: Tradiční regionální Node.js
Uživatel v Tokiu → Origin ve Virginii: 140ms
+ DB dotaz 1 (stejný region): 2ms
+ DB dotaz 2 (stejný region): 2ms
+ Zpracování: 5ms
= Celkem: ~149ms
Architektura B: Edge funkce s HTTP databází
Uživatel v Tokiu → Edge v Tokiu: 5ms
+ DB dotaz 1 (HTTP do Virginie): 145ms
+ DB dotaz 2 (HTTP do Virginie): 145ms
+ Zpracování: 3ms
= Celkem: ~298ms ← POMALEJŠÍ než regionální
Architektura C: Edge funkce s regionální databází (read replika)
Uživatel v Tokiu → Edge v Tokiu: 5ms
+ DB dotaz 1 (HTTP do repliky v Tokiu): 8ms
+ DB dotaz 2 (HTTP do repliky v Tokiu): 8ms
+ Zpracování: 3ms
= Celkem: ~24ms ← Nejrychlejší, ale vyžaduje multi-region DB
Architektura D: Edge pro Auth + regionální pro data
Uživatel v Tokiu → Edge middleware v Tokiu: 5ms (JWT ověření)
→ Origin ve Virginii: 140ms
+ DB dotaz 1 (stejný region): 2ms
+ DB dotaz 2 (stejný region): 2ms
+ Zpracování: 5ms
= Celkem: ~154ms
(Ale auth je již ověřen — origin nemusí znovu ověřovat)
(A neautorizované požadavky jsou blokovány na edge — nikdy nedorazí na origin)
Závěry:
- Edge + origin databáze = často pomalejší než jen použití regionálního serveru
- Edge + multi-region databáze = nejrychlejší ale nejdražší a nejsložitější
- Edge pro gatekeeping + regionální pro data = nejlepší pragmatická rovnováha
- Čistý edge (bez databáze) = neporazitelný pro věci jako přesměrování a auth kontroly
Architektura D je to, co používám pro většinu projektů. Edge zpracovává to, v čem je dobrý (rychlá rozhodnutí, auth, routing), a regionální Node.js server zpracovává to, v čem je dobrý (databázové dotazy, těžké výpočty).
Kdy Edge opravdu vítězí: operace bez databáze#
Matematika se kompletně obrátí, když není zapojena žádná databáze:
Přesměrování (edge):
Uživatel v Tokiu → Edge v Tokiu → přesměrovací odpověď: ~5ms
Přesměrování (regionální):
Uživatel v Tokiu → Origin ve Virginii → přesměrovací odpověď: ~280ms
Statická API odpověď (edge + cache):
Uživatel v Tokiu → Edge v Tokiu → cachovaná odpověď: ~5ms
Statická API odpověď (regionální):
Uživatel v Tokiu → Origin ve Virginii → odpověď: ~280ms
Blokování botů (edge):
Špatný bot kdekoliv → Edge (nejbližší) → 403 odpověď: ~5ms
(Bot se nikdy nedostane na váš origin server)
Blokování botů (regionální):
Špatný bot kdekoliv → Origin ve Virginii → 403 odpověď: ~280ms
(Bot stále spotřeboval zdroje originu)
Pro operace, které nepotřebují databázi, je edge 20-50x rychlejší. Toto není marketing — je to fyzika.
Můj rozhodovací framework#
Po roce práce s edge funkcemi v produkci, zde je vývojový diagram, který používám pro každý nový endpoint nebo kus logiky:
Krok 1: Potřebuje Node.js API?#
Pokud importuje fs, net, child_process nebo jakýkoliv nativní modul — Node.js regionální. Bez debaty.
Krok 2: Potřebuje databázové dotazy?#
Pokud ano a nemáte read repliky blízko vašich uživatelů — Node.js regionální (ve stejném regionu jako vaše databáze). Round-tripy k databázi budou dominovat.
Pokud ano a máte globálně distribuované read repliky — Edge může fungovat, pomocí HTTP databázových klientů.
Krok 3: Je to rozhodnutí o požadavku (routing, auth, přesměrování)?#
Pokud ano — Edge. Toto je sweet spot. Děláte rychlé rozhodnutí, které určuje, co se stane s požadavkem, než dorazí na origin.
Krok 4: Je odpověď cachovatelná?#
Pokud ano — Edge se správnými Cache-Control hlavičkami. I když první požadavek jde na váš origin, následné požadavky servírují z edge cache.
Krok 5: Je to CPU-intenzivní?#
Pokud zahrnuje významný výpočet (zpracování obrázků, generování PDF, velké datové transformace) — Node.js regionální.
Krok 6: Jak citlivé na latenci to je?#
Pokud je to background job nebo webhook — Node.js regionální. Nikdo na to nečeká. Pokud je to uživatelsky orientovaný požadavek, kde záleží na každé ms — Edge, pokud splňuje ostatní kritéria.
Tahák#
// ✅ PERFEKTNÍ pro edge
// - Middleware (auth, přesměrování, přepisy, hlavičky)
// - Geolokační logika
// - Přiřazení A/B testu
// - Detekce botů / WAF pravidla
// - Cache-friendly API odpovědi
// - Kontroly feature flagů
// - CORS preflight odpovědi
// - Statické datové transformace (bez DB)
// - Ověření podpisu webhooku
// ❌ PONECHTE na Node.js regionálním
// - Databázové CRUD operace
// - Nahrávání / zpracování souborů
// - Manipulace s obrázky
// - Generování PDF
// - Odesílání emailů (použijte HTTP API, ale stále regionální)
// - WebSocket servery
// - Background joby / fronty
// - Cokoliv používající nativní npm balíčky
// - SSR stránky s databázovými dotazy
// - GraphQL resolvery, které volají databáze
// 🤔 ZÁLEŽÍ NA OKOLNOSTECH
// - Autentizace (edge pro JWT, regionální pro session-DB)
// - API routy (edge pokud bez DB, regionální pokud DB)
// - Server-renderované stránky (edge pokud data z cache/fetch, regionální pokud DB)
// - Real-time funkce (edge pro úvodní auth, regionální pro perzistentní spojení)Co skutečně provozuji na Edge#
Pro tento web, zde je rozložení:
Edge (middleware):
- Detekce locale a přesměrování
- Filtrování botů
- Bezpečnostní hlavičky (CSP, HSTS, atd.)
- Logování přístupu
- Rate limiting (základní)
Node.js regionální:
- Renderování obsahu blogu (MDX zpracování potřebuje Node.js API přes Velite)
- API routy, které pracují s Redis
- Generování OG obrázků (potřebuje více CPU času)
- Generování RSS feedu
Statické (žádný runtime vůbec):
- Stránky nástrojů (pre-renderované při buildu)
- Stránky blogových příspěvků (pre-renderované při buildu)
- Všechny obrázky a assety (servírované přes CDN)
Nejlepší runtime je často žádný runtime. Pokud můžete něco pre-renderovat při buildu a servírovat jako statický asset, bude to vždy rychlejší než jakákoliv edge funkce. Edge je pro věci, které opravdu potřebují být dynamické při každém požadavku.
Upřímné shrnutí#
Edge funkce nejsou náhradou za tradiční servery. Jsou doplňkem. Jsou dalším nástrojem ve vašem architektonickém arzenálu — jedním, který je neuvěřitelně mocný pro správné use casy a aktivně škodlivý pro špatné.
Heuristika, ke které se neustále vracím: pokud vaše funkce potřebuje dosáhnout na databázi v jednom regionu, umístění funkce na edge nepomáhá — škodí. Právě jste přidali hop. Funkce běží rychleji, ale pak stráví 100ms+ voláním zpět na databázi. Čistý výsledek: pomalejší než běh všeho v jednom regionu.
Ale pro rozhodnutí, která mohou být učiněna pouze s informacemi v samotném požadavku — geolokace, cookies, hlavičky, JWT — je edge neporazitelný. Těch 5ms edge odpovědí nejsou syntetické benchmarky. Jsou reálné a vaši uživatelé cítí ten rozdíl.
Nepřesouvejte všechno na edge. Nenechávejte všechno mimo edge. Umístěte každý kus logiky tam, kde mu fyzika přeje.