Edge Functions: क्या हैं, कब इस्तेमाल करें, और कब नहीं
Edge runtime, V8 isolates, cold start का myth, geo-routing, A/B testing, edge पर auth, और कुछ चीज़ें मैंने वापस Node.js पर क्यों ले गईं। Edge computing पर एक संतुलित नज़र।
"Edge" शब्द बहुत इस्तेमाल होता है। Vercel कहता है। Cloudflare कहता है। Deno कहता है। Web performance पर हर conference talk अनिवार्य रूप से "running at the edge" का ज़िक्र करता है जैसे कोई जादुई मंत्र हो जो आपकी app को तेज़ बना देता है।
मैंने इस पर यकीन किया। मैंने middleware, API routes, यहां तक कि कुछ rendering logic भी edge runtime पर ले गया। कुछ फैसले शानदार थे। बाकी मैंने चुपचाप तीन हफ़्ते बाद Node.js पर वापस ले आया, रात 2 बजे connection pool errors debug करने के बाद।
यह पोस्ट उस कहानी का संतुलित संस्करण है — edge वास्तव में क्या है, कहां यह सच में चमकता है, कहां बिल्कुल नहीं, और मैं अपनी application के हर हिस्से के लिए कौन सा runtime इस्तेमाल करूं यह कैसे तय करता हूं।
Edge क्या है?#
भूगोल से शुरू करते हैं। जब कोई आपकी website visit करता है, उनकी request उनके device से, ISP के ज़रिए, internet के पार आपके server तक जाती है, process होती है, और response वापस सारा रास्ता तय करके आता है। अगर आपका server us-east-1 (Virginia) में है और user Tokyo में, वो round trip लगभग 14,000 km cover करता है। Fiber में light की speed पर, यह एक तरफ़ लगभग 70ms है सिर्फ़ physics की वजह से। DNS resolution, TLS handshake, और processing time जोड़ लीजिए, और user को एक byte दिखने से पहले आसानी से 200-400ms लग जाते हैं।
"Edge" का मतलब है अपना code globally distributed servers पर चलाना — वही CDN nodes जो हमेशा static assets serve करते रहे हैं, लेकिन अब वो आपकी logic भी execute कर सकते हैं। Virginia में एक origin server की बजाय, आपका code 300+ locations पर दुनियाभर में चलता है। Tokyo में user Tokyo का server hit करता है। Paris में user Paris का server।
Latency का हिसाब सीधा और प्रभावशाली है:
Traditional (single origin):
Tokyo → Virginia: ~140ms round trip (सिर्फ़ physics)
+ TLS handshake: ~140ms और (एक और round trip)
+ Processing: 20-50ms
Total: ~300-330ms
Edge (local PoP):
Tokyo → Tokyo edge node: ~5ms round trip
+ TLS handshake: ~5ms और
+ Processing: 5-20ms
Total: ~15-30ms
Initial response के लिए 10-20x सुधार। यह वास्तविक है, मापने योग्य है, और कुछ operations के लिए transformative है।
लेकिन marketing जो छिपाती है वो यह है: edge एक पूर्ण server environment नहीं है। यह मूल रूप से कुछ अलग चीज़ है।
V8 Isolates बनाम Node.js#
Traditional Node.js एक पूर्ण operating system process में चलता है। इसके पास filesystem access है, यह TCP connections खोल सकता है, child processes spawn कर सकता है, environment variables को stream के रूप में पढ़ सकता है, यह अनिवार्य रूप से वो सब कुछ कर सकता है जो एक Linux process कर सकता है।
Edge functions Node.js पर नहीं चलतीं। ये V8 isolates पर चलती हैं — वही JavaScript engine जो Chrome को power करता है, लेकिन अपने मूल रूप में stripped down। V8 isolate को एक lightweight sandbox समझिए:
// यह Node.js में काम करता है लेकिन edge पर नहीं
import fs from "fs";
import { createConnection } from "net";
import { execSync } from "child_process";
const file = fs.readFileSync("/etc/hosts"); // ❌ कोई filesystem नहीं
const conn = createConnection({ port: 5432 }); // ❌ कोई raw TCP नहीं
const result = execSync("ls -la"); // ❌ कोई child processes नहीं
process.env.DATABASE_URL; // ⚠️ उपलब्ध लेकिन static, deploy time पर setEdge पर आपके पास Web API surface है — वही APIs जो browser में उपलब्ध हैं:
// ये सब 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());सीमाएं वास्तविक और कठोर हैं:
- Memory: 128MB प्रति isolate (Cloudflare Workers), कुछ platforms पर 256MB
- CPU time: 10-50ms वास्तविक CPU time (wall clock time नहीं —
await fetch()count नहीं होता, लेकिन 5MB payload परJSON.parse()होता है) - कोई native modules नहीं: C++ binding वाली कोई भी चीज़ (bcrypt, sharp, canvas) बाहर है
- कोई persistent connections नहीं: requests के बीच database connection खुला नहीं रख सकते
- Bundle size limits: आमतौर पर पूरी worker script के लिए 1-5MB
यह CDN पर Node.js नहीं है। यह एक अलग runtime है एक अलग mental model के साथ।
Cold Starts: Myth और Reality#
शायद आपने सुना होगा कि edge functions में "zero cold starts" होते हैं। यह... ज़्यादातर सच है, और तुलना वास्तव में नाटकीय है।
एक traditional container-based serverless function (AWS Lambda, Google Cloud Functions) इस तरह काम करती है:
- Request आता है
- Platform एक container provision करता है (अगर कोई उपलब्ध नहीं)
- Container OS boot करता है
- Runtime initialize होता है (Node.js, Python, आदि)
- आपका code load और initialize होता है
- Request process होता है
Step 2-5 cold start है। Node.js Lambda के लिए यह आमतौर पर 200-500ms है। Java Lambda के लिए 2-5 seconds हो सकता है। .NET Lambda के लिए 500ms-1.5s।
V8 isolates अलग तरह काम करते हैं:
- Request आता है
- Platform एक नया V8 isolate बनाता है (या warm वाला reuse करता है)
- आपका code load होता है (deploy time पर पहले से bytecode में compile हो चुका)
- Request process होता है
Step 2-3 में 5ms से कम लगता है। अक्सर 1ms से कम। Isolate कोई container नहीं है — कोई OS boot नहीं करना, कोई runtime initialize नहीं करना। V8 microseconds में एक fresh isolate बना लेता है। "Zero cold start" वाली बात marketing है, लेकिन reality (sub-5ms startup) zero के इतना करीब है कि ज़्यादातर use cases में फ़र्क नहीं पड़ता।
लेकिन cold starts कब आपको काटती हैं edge पर:
बड़े bundles। अगर आपकी edge function 2MB dependencies pull करती है, वो code अभी भी load और parse होना चाहिए। मैंने यह कठिन तरीके से सीखा जब एक validation library और date formatting library edge middleware में bundle कर दी। Cold start 2ms से 40ms हो गया। अभी भी तेज़, लेकिन "zero" नहीं।
दुर्लभ locations। Edge providers के सैकड़ों PoPs हैं, लेकिन सभी PoPs आपका code warm नहीं रखते। अगर Nairobi से प्रति घंटा एक request आती है, वो isolate requests के बीच recycle हो जाता है। अगली request फिर startup cost चुकाती है।
प्रति request एकाधिक isolates। अगर आपकी edge function दूसरी edge function को call करती है (या अगर middleware और API route दोनों edge हैं), आप एक user request के लिए कई isolates spin up कर सकते हैं।
व्यावहारिक सलाह: अपने edge function bundles छोटे रखें। सिर्फ़ वही import करें जो चाहिए। आक्रामक रूप से tree-shake करें। Bundle जितना छोटा, cold start उतना तेज़, "zero cold start" का वादा उतना ही खरा उतरता है।
// ❌ Edge पर ऐसा मत करो
import dayjs from "dayjs";
import * as yup from "yup";
import lodash from "lodash";
// ✅ इसकी बजाय built-in APIs इस्तेमाल करो
const date = new Date().toISOString();
const isValid = typeof input === "string" && input.length < 200;
const unique = [...new Set(items)];Edge Functions के लिए उत्तम Use Cases#
व्यापक प्रयोग करने के बाद, मुझे एक स्पष्ट pattern मिला: edge functions तब उत्कृष्ट होती हैं जब आपको origin server तक पहुंचने से पहले request के बारे में तेज़ निर्णय लेना हो। ये gatekeepers, routers, और transformers हैं — application servers नहीं।
1. Geolocation-Based Redirects#
यह सबसे बढ़िया use case है। Request निकटतम edge node को hit करता है, जो पहले से जानता है user कहां है। कोई API call नहीं चाहिए, कोई IP lookup database नहीं — platform geo data प्रदान करता है:
// middleware.ts — हर request पर edge पर चलता है
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";
// Country-specific store पर redirect करें
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));
}
}
// Downstream उपयोग के लिए geo headers जोड़ें
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;
}यह 5ms से कम में चलता है, user के ठीक बगल में। विकल्प — request को पूरा origin server तक भेजना सिर्फ़ IP lookup और redirect के लिए — origin से दूर users के लिए 100-300ms खर्च करता।
2. Client Flicker के बिना A/B Testing#
Client-side A/B testing डरावना "flash of original content" पैदा करता है — user एक पल के लिए version A देखता है, फिर JavaScript version B swap कर देता है। Edge पर, page render शुरू होने से पहले ही variant assign हो सकता है:
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
// जांचें कि user के पास पहले से variant assignment है या नहीं
const existingVariant = request.cookies.get("ab-variant")?.value;
if (existingVariant) {
// सही variant page पर rewrite करें
const url = request.nextUrl.clone();
url.pathname = `/variants/${existingVariant}${url.pathname}`;
return NextResponse.rewrite(url);
}
// नया variant assign करें (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 दिन
httpOnly: true,
sameSite: "lax",
});
return response;
}User को कभी flicker नहीं दिखता क्योंकि rewrite network level पर होता है। Browser को पता ही नहीं चलता कि यह A/B test था — उसे सीधे variant page मिलता है।
3. Auth Token Verification#
अगर आपका auth JWTs इस्तेमाल करता है (और database session lookups नहीं करता), edge एकदम सही है। JWT verification शुद्ध crypto है — कोई database ज़रूरत नहीं:
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",
});
// User info downstream को trusted headers के रूप में भेजें
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 expired या invalid
const response = NextResponse.redirect(new URL("/login", request.url));
response.cookies.delete("session-token");
return response;
}
}यह pattern शक्तिशाली है: edge middleware token verify करता है और user information आपके origin को trusted headers के रूप में भेजता है। आपके API routes को token फिर से verify नहीं करना पड़ता — वे बस request.headers.get("x-user-id") पढ़ते हैं।
4. Bot Detection और Rate Limiting#
Edge functions अवांछित traffic को origin तक पहुंचने से पहले ही block कर सकती हैं:
import { NextRequest, NextResponse } from "next/server";
// सरल in-memory rate limiter (प्रति edge location)
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") ?? "";
// ज्ञात बुरे bots को block करें
const badBots = ["AhrefsBot", "SemrushBot", "MJ12bot", "DotBot"];
if (badBots.some((bot) => ua.includes(bot))) {
return new NextResponse("Forbidden", { status: 403 });
}
// सरल rate limiting
const now = Date.now();
const windowMs = 60_000; // 1 मिनट
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 });
}
// Memory leak रोकने के लिए समय-समय पर सफाई
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();
}एक चेतावनी: ऊपर rate limit map प्रति-isolate, प्रति-location है। अगर आपके 300 edge locations हैं, हर एक का अपना map है। सख्त rate limiting के लिए, Upstash Redis या Cloudflare Durable Objects जैसा distributed store चाहिए। लेकिन मोटे तौर पर दुरुपयोग रोकने के लिए, प्रति-location limits आश्चर्यजनक रूप से अच्छा काम करती हैं।
5. Request Rewriting और Personalization Headers#
Edge functions requests को origin तक पहुंचने से पहले transform करने में उत्कृष्ट हैं:
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
const response = NextResponse.next();
const url = request.nextUrl;
// Device-based content negotiation
const ua = request.headers.get("user-agent") ?? "";
const isMobile = /mobile|android|iphone/i.test(ua);
response.headers.set("x-device-type", isMobile ? "mobile" : "desktop");
// Cookie से feature flags
const flags = request.cookies.get("feature-flags")?.value;
if (flags) {
response.headers.set("x-feature-flags", flags);
}
// i18n के लिए locale detection
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;
}Edge कहां विफल होता है#
यह वो section है जो marketing pages छोड़ देती हैं। मैंने इनमें से हर एक दीवार से टकराया हूं।
1. Database Connections#
यह सबसे बड़ी बात है। Traditional databases (PostgreSQL, MySQL) persistent TCP connections इस्तेमाल करती हैं। Node.js server startup पर connection pool खोलता है और उन connections को requests में reuse करता है। कुशल, आज़माया हुआ, अच्छी तरह समझा गया।
Edge functions ऐसा नहीं कर सकतीं। हर isolate अस्थायी है। कोई "startup" चरण नहीं है जहां आप connections खोलें। भले ही आप connection खोल सकें, isolate एक request के बाद recycle हो सकता है, connection setup time बर्बाद करते हुए।
// ❌ यह pattern मूल रूप से edge पर काम नहीं करता
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10, // 10 का Connection pool
});
// हर edge invocation:
// 1. नया Pool बनाएगा (invocations में विश्वसनीय रूप से reuse नहीं हो सकता)
// 2. Database से TCP connection खोलेगा (जो us-east-1 में है, edge पर नहीं)
// 3. Database के साथ TLS handshake करेगा
// 4. Query चलाएगा
// 5. Isolate recycle होने पर connection छोड़ देगाDatabase round-trip की समस्या मूलभूत है। आपका database एक region में है। आपकी edge function 300 regions में। Edge से हर database query edge location से database region तक जाती है और वापस। Tokyo में edge node पर user के लिए, लेकिन database Virginia में:
Edge function Tokyo में
→ PostgreSQL Virginia में query: ~140ms round trip
→ दूसरी query: ~140ms और
→ Total: 280ms सिर्फ़ दो queries के लिए
Node.js function Virginia में (DB के same region)
→ PostgreSQL query: ~1ms round trip
→ दूसरी query: ~1ms और
→ Total: 2ms दो queries के लिए
इस परिदृश्य में database operations के लिए edge function 140x धीमा है। इससे फ़र्क नहीं पड़ता कि edge function तेज़ शुरू हुआ — database round trips सब कुछ dominate कर लेते हैं।
यही कारण है कि HTTP-based database proxies मौजूद हैं (Neon का serverless driver, PlanetScale का fetch-based driver, Supabase का REST API)। ये काम करते हैं, लेकिन ये अभी भी एक ही region में database को HTTP requests भेज रहे हैं। ये "TCP इस्तेमाल नहीं कर सकते" समस्या हल करते हैं लेकिन "database दूर है" समस्या नहीं।
// ✅ यह edge पर काम करता है (HTTP-based database access)
// लेकिन अभी भी धीमा है अगर database edge node से दूर है
import { neon } from "@neondatabase/serverless";
export const runtime = "edge";
export async function GET(request: Request) {
const sql = neon(process.env.DATABASE_URL!);
// यह आपके Neon database को HTTP request करता है
// काम करता है, लेकिन latency database region की दूरी पर निर्भर
const posts = await sql`SELECT * FROM posts WHERE published = true LIMIT 10`;
return Response.json(posts);
}2. लंबे समय चलने वाले Tasks#
Edge functions में CPU time limits हैं, आमतौर पर 10-50ms वास्तविक compute time। Wall clock time ज़्यादा उदार है (आमतौर पर 30 seconds), लेकिन CPU-intensive operations सीमा जल्दी hit करती हैं:
// ❌ ये edge पर CPU time limits पार कर जाएंगे
export const runtime = "edge";
export async function POST(request: Request) {
const data = await request.json();
// Image processing — CPU intensive
// (sharp भी इस्तेमाल नहीं कर सकते क्योंकि यह native module है)
const processed = heavyImageProcessing(data.image);
// PDF generation — CPU intensive + Node.js APIs चाहिए
const pdf = generatePDF(data.content);
// बड़ा data transformation
const result = data.items // 100,000 items
.map(transform)
.filter(validate)
.sort(compare)
.reduce(aggregate, {});
return Response.json(result);
}अगर आपकी function को कुछ milliseconds से ज़्यादा CPU time चाहिए, यह regional Node.js server पर belong करती है। बस।
3. Node.js-Only Dependencies#
यह लोगों को अचंभित करता है। आश्चर्यजनक संख्या में npm packages Node.js built-in modules पर निर्भर करते हैं:
// ❌ ये packages edge पर काम नहीं करेंगे
import bcrypt from "bcrypt"; // Native C++ binding
import sharp from "sharp"; // Native C++ binding
import puppeteer from "puppeteer"; // Filesystem + child_process चाहिए
import nodemailer from "nodemailer"; // Net module चाहिए
import { readFile } from "fs/promises"; // Node.js filesystem API
import mongoose from "mongoose"; // TCP connections + Node.js APIs
// ✅ Edge-compatible विकल्प
import { hashSync } from "bcryptjs"; // शुद्ध JS implementation (धीमा)
// Images के लिए: अलग service या API इस्तेमाल करें
// Email के लिए: HTTP-based email API इस्तेमाल करें (Resend, SendGrid REST)
// Database के लिए: HTTP-based clients इस्तेमाल करेंEdge पर कुछ भी ले जाने से पहले, हर dependency जांचें। एक require("fs") आपकी dependency tree में तीन levels गहरे छिपा — आपकी edge function runtime पर crash करेगा, build time पर नहीं। आप deploy करेंगे, सब ठीक लगेगा, फिर पहली request उस code path पर पहुंचेगी और एक गूढ़ error मिलेगी।
4. बड़े Bundle Sizes#
Edge platforms में सख्त bundle size limits हैं:
- Cloudflare Workers: 1MB (free), 5MB (paid)
- Vercel Edge Functions: 4MB (compressed)
- Deno Deploy: 20MB
यह पर्याप्त लगता है जब तक आप एक UI component library, validation library, और date library import नहीं करते। मेरी एक edge middleware एक बार 3.5MB तक फूल गई क्योंकि मैंने एक barrel file से import किया जिसने पूरी @/components directory pull कर ली।
// ❌ Barrel file imports बहुत ज़्यादा pull कर सकते हैं
import { validateEmail } from "@/lib/utils";
// अगर utils.ts 20 अन्य modules से re-export करता है, सभी bundle होते हैं
// ✅ सीधे source से import करें
import { validateEmail } from "@/lib/validators/email";5. Streaming और WebSockets#
Edge functions streaming responses कर सकती हैं (Web Streams API), लेकिन लंबे समय तक चलने वाले WebSocket connections अलग कहानी हैं। कुछ platforms edge पर WebSockets support करते हैं (Cloudflare Workers, Deno Deploy), लेकिन edge functions की अस्थायी प्रकृति उन्हें stateful, लंबे समय तक चलने वाले connections के लिए अनुपयुक्त बनाती है।
Next.js Edge Runtime#
Next.js प्रति-route आधार पर edge runtime opt-in करना सीधा बनाता है। आपको पूरा all-in नहीं जाना — आप ठीक वो routes चुनते हैं जो edge पर चलें।
Middleware (हमेशा Edge)#
Next.js middleware हमेशा edge पर चलता है। यह design के अनुसार है — middleware हर matching request को intercept करता है, इसलिए इसे तेज़ और globally distributed होना चाहिए:
// middleware.ts — हमेशा edge पर चलता है, opt-in की ज़रूरत नहीं
import { NextRequest, NextResponse } from "next/server";
export function middleware(request: NextRequest) {
// यह हर matching request से पहले चलता है
// तेज़ रखें — कोई database calls नहीं, कोई भारी computation नहीं
return NextResponse.next();
}
export const config = {
// सिर्फ़ specific paths पर चलाएं
matcher: [
"/((?!_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml).*)",
],
};Edge पर API Routes#
कोई भी route handler edge runtime opt-in कर सकता है:
// app/api/hello/route.ts
export const runtime = "edge"; // यह एक line runtime बदल देती है
export async function GET(request: Request) {
return Response.json({
message: "Hello from the edge",
region: process.env.VERCEL_REGION ?? "unknown",
timestamp: Date.now(),
});
}Edge पर Page Routes#
पूरे pages भी edge पर render हो सकते हैं, हालांकि मैं ऐसा करने से पहले ध्यान से सोचूंगा:
// app/dashboard/page.tsx
export const runtime = "edge";
export default async function DashboardPage() {
// याद रखें: यहां कोई Node.js APIs नहीं
// कोई भी data fetching fetch() या edge-compatible clients इस्तेमाल करनी चाहिए
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>
{/* data render करें */}
</main>
);
}Edge Runtime में क्या उपलब्ध है#
एक व्यावहारिक संदर्भ कि आप क्या इस्तेमाल कर सकते हैं और क्या नहीं:
// ✅ Edge पर उपलब्ध
fetch() // HTTP requests
Request / Response // Web standard request/response
Headers // HTTP headers
URL / URLSearchParams // URL parsing
TextEncoder / TextDecoder // String encoding
crypto.subtle // Crypto operations (signing, hashing)
crypto.randomUUID() // UUID generation
crypto.getRandomValues() // Cryptographic random numbers
structuredClone() // Deep cloning
atob() / btoa() // Base64 encoding/decoding
setTimeout() / setInterval() // Timers (लेकिन CPU limits याद रखें)
console.log() // Logging
ReadableStream / WritableStream // Streaming
AbortController / AbortSignal // Request cancellation
URLPattern // URL pattern matching
// ❌ Edge पर उपलब्ध नहीं
require() // CommonJS (import इस्तेमाल करें)
fs / path / os // Node.js built-in modules
process.exit() // Process control
Buffer // Uint8Array इस्तेमाल करें
__dirname / __filename // import.meta.url इस्तेमाल करें
setImmediate() // Web standard नहीं हैEdge पर Auth: पूर्ण Pattern#
मैं authentication पर और गहराई में जाना चाहता हूं क्योंकि यह सबसे प्रभावशाली edge use cases में से एक है, लेकिन इसे गलत करना भी आसान है।
जो pattern काम करता है वो है: edge पर token verify करें, विश्वसनीय claims downstream भेजें, middleware में कभी database को touch न करें।
// lib/edge-auth.ts — Edge-compatible auth utilities
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 seconds clock skew tolerance
});
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;
// Public paths के लिए auth skip करें
if (PUBLIC_PATHS.some((p) => pathname === p || pathname.startsWith("/api/public"))) {
return NextResponse.next();
}
// Token निकालें
const token = request.cookies.get("auth-token")?.value;
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
// Token verify करें (शुद्ध crypto — कोई database call नहीं)
const payload = await verifyToken(token);
if (!payload) {
const response = NextResponse.redirect(new URL("/login", request.url));
response.cookies.delete("auth-token");
return response;
}
// Role-based access control
if (ADMIN_PATHS.some((p) => pathname.startsWith(p)) && payload.role !== "admin") {
return NextResponse.redirect(new URL("/unauthorized", request.url));
}
// Verified user info origin को trusted headers के रूप में भेजें
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);
// Signal करें अगर token को refresh चाहिए
if (isTokenExpiringSoon(payload)) {
response.headers.set("x-token-refresh", "true");
}
return response;
}// app/api/profile/route.ts — Origin server trusted headers पढ़ता है
export async function GET(request: Request) {
// ये headers edge middleware ने JWT verification के बाद set किए
// ये विश्वसनीय हैं क्योंकि ये हमारे अपने infrastructure से आते हैं
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 });
}
// अब हम database hit कर सकते हैं — हम origin server पर हैं,
// database के ठीक बगल में, connection pool के साथ
const user = await db.user.findUnique({ where: { id: userId } });
return Response.json(user);
}मुख्य अंतर्दृष्टि: edge तेज़ हिस्सा करता है (crypto verification), और origin धीमा हिस्सा करता है (database queries)। हर वो जगह चलता है जहां सबसे कुशल है।
एक महत्वपूर्ण चेतावनी: यह सिर्फ़ JWTs के लिए काम करता है। अगर आपकी auth system को हर request पर database lookup चाहिए (जैसे session-based auth session ID cookie के साथ), edge मदद नहीं कर सकता — आपको अभी भी database call करनी होगी, जिसका मतलब origin region तक round trip।
Edge Caching#
Edge पर caching वो जगह है जहां चीज़ें दिलचस्प हो जाती हैं। Edge nodes responses cache कर सकते हैं, जिसका मतलब बाद की requests same URL पर origin को hit किए बिना सीधे edge से serve होती हैं।
Cache-Control सही तरीके से#
// 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: {
// CDN पर 60 seconds cache करें
// Revalidate करते समय 5 minutes तक stale serve करें
// Client 10 seconds cache कर सकता है
"Cache-Control": "public, s-maxage=60, stale-while-revalidate=300, max-age=10",
// इन headers से vary करें ताकि अलग variants को अलग cache entries मिलें
Vary: "Accept-Language, Accept-Encoding",
// Targeted invalidation के लिए CDN-specific cache tag
"Cache-Tag": `products,category-${category}`,
},
});
}stale-while-revalidate pattern edge पर विशेष रूप से शक्तिशाली है। देखिए क्या होता है:
- पहली request: Edge origin से fetch करता है, response cache करता है, return करता है
- 60 seconds के भीतर requests: Edge cache से serve करता है (0ms origin latency)
- 61-360 seconds पर request: Edge तुरंत stale cached version serve करता है, लेकिन background में origin से fresh version fetch करता है
- 360 seconds के बाद: Cache पूरी तरह expire, अगली request origin को जाती है
आपके users लगभग हमेशा cached response पाते हैं। Freshness tradeoff स्पष्ट और tunable है।
Dynamic Configuration के लिए Edge Config#
Vercel का Edge Config (और अन्य platforms से मिलती-जुलती services) आपको key-value configuration store करने देता है जो हर edge location पर replicate होती है। Feature flags, redirect rules, और A/B test configuration के लिए बहुत उपयोगी जिन्हें बिना redeploy किए update करना हो:
import { get } from "@vercel/edge-config";
import { NextRequest, NextResponse } from "next/server";
export async function middleware(request: NextRequest) {
// Edge Config reads बहुत तेज़ हैं (~1ms) क्योंकि
// data हर edge location पर replicate है
const maintenanceMode = await get<boolean>("maintenance_mode");
if (maintenanceMode) {
return NextResponse.rewrite(new URL("/maintenance", request.url));
}
// Feature flags
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));
}
// Dynamic redirects (बिना redeploy redirects update करें)
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();
}यह वास्तव में game-changer है। Edge Config से पहले, feature flag बदलने का मतलब code change और redeploy था। अब आप dashboard में एक JSON value update करते हैं और यह seconds में globally propagate होता है।
असली Performance Math#
Marketing math की बजाय ईमानदार math करते हैं। मैं एक typical API endpoint के लिए तीन architectures की तुलना करूंगा जिसे database query करनी है:
Scenario: User Profile API (2 database queries)#
Architecture A: Traditional Regional Node.js
User Tokyo में → Origin Virginia: 140ms
+ DB query 1 (same region): 2ms
+ DB query 2 (same region): 2ms
+ Processing: 5ms
= Total: ~149ms
Architecture B: HTTP Database के साथ Edge Function
User Tokyo में → Edge Tokyo: 5ms
+ DB query 1 (HTTP Virginia): 145ms
+ DB query 2 (HTTP Virginia): 145ms
+ Processing: 3ms
= Total: ~298ms ← regional से SLOWER
Architecture C: Regional Database (read replica) के साथ Edge Function
User Tokyo में → Edge Tokyo: 5ms
+ DB query 1 (HTTP Tokyo replica): 8ms
+ DB query 2 (HTTP Tokyo replica): 8ms
+ Processing: 3ms
= Total: ~24ms ← सबसे तेज़, लेकिन multi-region DB चाहिए
Architecture D: Auth के लिए Edge + Data के लिए Regional
User Tokyo में → Edge middleware Tokyo: 5ms (JWT verify)
→ Origin Virginia: 140ms
+ DB query 1 (same region): 2ms
+ DB query 2 (same region): 2ms
+ Processing: 5ms
= Total: ~154ms
(लेकिन auth पहले से verified — origin को re-verify नहीं करना)
(और unauthorized requests edge पर ही block — origin तक कभी नहीं पहुंचतीं)
निष्कर्ष:
- Edge + origin database = अक्सर धीमा regional server से
- Edge + multi-region database = सबसे तेज़ लेकिन सबसे महंगा और जटिल
- Edge gatekeeping + regional data = सबसे अच्छा व्यावहारिक संतुलन
- शुद्ध edge (कोई database नहीं) = अजेय redirects और auth checks जैसी चीज़ों के लिए
Architecture D वो है जो मैं ज़्यादातर projects में इस्तेमाल करता हूं। Edge वो handle करता है जिसमें अच्छा है (तेज़ निर्णय, auth, routing), और regional Node.js server वो handle करता है जिसमें अच्छा है (database queries, भारी computation)।
जब Edge सच में जीतता है: बिना-Database Operations#
Math पूरी तरह पलट जाता है जब कोई database involved नहीं:
Redirect (edge):
User Tokyo में → Edge Tokyo → redirect response: ~5ms
Redirect (regional):
User Tokyo में → Origin Virginia → redirect response: ~280ms
Static API response (edge + cache):
User Tokyo में → Edge Tokyo → cached response: ~5ms
Static API response (regional):
User Tokyo में → Origin Virginia → response: ~280ms
Bot blocking (edge):
बुरा bot कहीं भी → Edge (निकटतम) → 403 response: ~5ms
(Bot कभी origin server तक नहीं पहुंचता)
Bot blocking (regional):
बुरा bot कहीं भी → Origin Virginia → 403 response: ~280ms
(Bot ने अभी भी origin resources consume किए)
जिन operations को database नहीं चाहिए, उनके लिए edge 20-50x तेज़ है। यह marketing नहीं है — यह physics है।
मेरा Decision Framework#
Edge functions के साथ production में एक साल काम करने के बाद, यह वो flowchart है जो मैं हर नए endpoint या logic के टुकड़े के लिए इस्तेमाल करता हूं:
Step 1: क्या Node.js APIs चाहिए?#
अगर fs, net, child_process, या कोई native module import करता है — Node.js regional। कोई बहस नहीं।
Step 2: क्या Database queries चाहिए?#
अगर हां, और आपके users के पास read replicas नहीं हैं — Node.js regional (database के same region में)। Database round trips सब dominate करेंगे।
अगर हां, और आपके पास globally distributed read replicas हैं — Edge काम कर सकता है, HTTP-based database clients के साथ।
Step 3: क्या यह request के बारे में निर्णय है (routing, auth, redirect)?#
अगर हां — Edge। यह sweet spot है। आप एक तेज़ निर्णय ले रहे हैं जो तय करता है request का क्या होगा इससे पहले कि वो origin तक पहुंचे।
Step 4: क्या Response cacheable है?#
अगर हां — Edge उचित Cache-Control headers के साथ। भले ही पहली request origin को जाए, बाद की requests edge cache से serve होती हैं।
Step 5: क्या यह CPU-intensive है?#
अगर इसमें significant computation शामिल है (image processing, PDF generation, बड़े data transforms) — Node.js regional।
Step 6: कितनी Latency-sensitive है?#
अगर background job या webhook है — Node.js regional। कोई इंतज़ार नहीं कर रहा। अगर user-facing request है जहां हर ms मायने रखता है — Edge, अगर बाकी criteria पूरे होते हैं।
Cheat Sheet#
// ✅ Edge के लिए एकदम सही
// - Middleware (auth, redirects, rewrites, headers)
// - Geolocation logic
// - A/B test assignment
// - Bot detection / WAF rules
// - Cache-friendly API responses
// - Feature flag checks
// - CORS preflight responses
// - Static data transformations (कोई DB नहीं)
// - Webhook signature verification
// ❌ Node.js regional पर रखें
// - Database CRUD operations
// - File uploads / processing
// - Image manipulation
// - PDF generation
// - Email sending (HTTP API इस्तेमाल करें, लेकिन फिर भी regional)
// - WebSocket servers
// - Background jobs / queues
// - Native npm packages इस्तेमाल करने वाली कोई भी चीज़
// - Database queries वाले SSR pages
// - Database hit करने वाले GraphQL resolvers
// 🤔 निर्भर करता है
// - Authentication (JWT के लिए edge, session-DB के लिए regional)
// - API routes (DB नहीं तो edge, DB है तो regional)
// - Server-rendered pages (data cache/fetch से है तो edge, DB से है तो regional)
// - Real-time features (शुरुआती auth के लिए edge, persistent connections के लिए regional)मैं वास्तव में Edge पर क्या चलाता हूं#
इस site के लिए, विभाजन यह है:
Edge (middleware):
- Locale detection और redirect
- Bot filtering
- Security headers (CSP, HSTS, आदि)
- Access logging
- Rate limiting (बुनियादी)
Node.js regional:
- Blog content rendering (MDX processing के लिए Velite के ज़रिए Node.js APIs चाहिए)
- API routes जो Redis touch करते हैं
- OG image generation (ज़्यादा CPU time चाहिए)
- RSS feed generation
Static (कोई runtime नहीं):
- Tool pages (build time पर pre-rendered)
- Blog post pages (build time पर pre-rendered)
- सभी images और assets (CDN-served)
सबसे अच्छा runtime अक्सर कोई runtime नहीं होता। अगर आप कुछ build time पर pre-render कर सकते हैं और static asset के रूप में serve कर सकते हैं, वो हमेशा किसी भी edge function से तेज़ होगा। Edge उन चीज़ों के लिए है जिन्हें वास्तव में हर request पर dynamic होना चाहिए।
ईमानदार सारांश#
Edge functions traditional servers का प्रतिस्थापन नहीं हैं। ये पूरक हैं। ये आपकी architecture toolbox में एक अतिरिक्त उपकरण हैं — जो सही use cases के लिए अविश्वसनीय रूप से शक्तिशाली है और गलत के लिए सक्रिय रूप से हानिकारक।
जो heuristic मुझे बार-बार काम आता है: अगर आपकी function को एक ही region में database तक पहुंचना है, function को edge पर रखने से मदद नहीं होती — नुकसान होता है। आपने बस एक hop जोड़ दिया। Function तेज़ चलता है, लेकिन फिर 100ms+ database तक वापस पहुंचने में बिताता है। अंतिम परिणाम: एक ही region में सब कुछ चलाने से धीमा।
लेकिन उन निर्णयों के लिए जो केवल request में मौजूद जानकारी से बन सकते हैं — geolocation, cookies, headers, JWTs — edge अपराजेय है। वो 5ms edge responses कृत्रिम benchmarks नहीं हैं। वास्तविक हैं, और आपके users फ़र्क महसूस करते हैं।
सब कुछ edge पर मत ले जाइए। सब कुछ edge से दूर मत रखिए। हर logic का टुकड़ा वहां रखिए जहां physics उसका साथ देती है।