Edge Runtime Constraints
Tổng quan Constraints
Khi chạy trên Edge (Cloudflare Workers), bạn phải tuân thủ các giới hạn sau:
┌─────────────────────────────────────────────────────────────┐
│ EDGE RUNTIME CONSTRAINTS │
├─────────────────────────────────────────────────────────────┤
│ ❌ No Node.js APIs (fs, path, child_process, etc.) │
│ ❌ No native npm modules (bcrypt, sharp, canvas) │
│ ❌ No long-running processes (> 30s free, 15min paid) │
│ ❌ No large memory usage (max 128MB) │
│ ❌ No global state persistence (stateless) │
├─────────────────────────────────────────────────────────────┤
│ ✅ Web Standard APIs (fetch, crypto, URL, etc.) │
│ ✅ Pure JavaScript npm packages │
│ ✅ Supabase, Prisma (edge client), etc. │
│ ✅ Streaming responses │
│ ✅ WebCrypto API │
└─────────────────────────────────────────────────────────────┘
Constraint 1: No Node.js APIs
Không hoạt động
// ❌ File system
import fs from 'fs';
import path from 'path';
// ❌ Process & OS
import os from 'os';
process.env.NODE_ENV; // ❌
// ❌ Child processes
import { exec } from 'child_process';
// ❌ Node crypto
import crypto from 'crypto';
crypto.createHash('sha256'); // ❌
Thay thế bằng Web APIs
// ✅ Environment variables
const apiKey = process.env.API_KEY; // Vẫn hoạt động qua adapter
// ✅ Web Crypto API
const hash = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode('hello')
);
// ✅ URL API
const url = new URL('https://example.com/path');
url.pathname; // '/path'
Constraint 2: No Native Modules
Không hoạt động
// ❌ Native crypto
import bcrypt from 'bcrypt';
await bcrypt.hash(password, 10);
// ❌ Image processing
import sharp from 'sharp';
await sharp(buffer).resize(200).toBuffer();
// ❌ PDF generation
import PDFDocument from 'pdfkit';
// ❌ Database drivers (native)
import pg from 'pg'; // Uses native bindings
Thay thế
// ✅ Pure JS password hashing
import { hash, verify } from '@node-rs/bcrypt'; // WASM-based
// hoặc
import { hashSync } from 'bcryptjs'; // Pure JS (slower)
// ✅ Image processing via external service
const result = await fetch('https://api.cloudflare.com/images/v1/transform', {
method: 'POST',
body: imageBuffer,
});
// ✅ Supabase client (pure JS)
import { createClient } from '@supabase/supabase-js';
Constraint 3: Memory Limit (128MB)
Vấn đề
// ❌ Large file processing
export async function POST(request: Request) {
const file = await request.arrayBuffer(); // 100MB file
// Memory exceeded!
}
// ❌ Large data aggregation
const allUsers = await db.user.findMany(); // 1M records
const processed = allUsers.map(transform); // Memory exceeded!
Giải pháp: Streaming
// ✅ Stream processing
export async function POST(request: Request) {
const reader = request.body?.getReader();
// Process in chunks
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Process chunk (< 128MB each)
await processChunk(value);
}
return new Response('Done');
}
// ✅ Pagination
const users = await db.user.findMany({
take: 100,
skip: page * 100,
});
Constraint 4: Timeout (30s / 15min)
Free tier: 30 seconds
// ❌ Long-running task
export async function POST(request: Request) {
for (let i = 0; i < 1000000; i++) {
await heavyComputation(i); // Timeout!
}
}
Giải pháp: Queue + Background Jobs
// ✅ Enqueue and return immediately
export async function POST(request: Request) {
const { taskId } = await request.json();
// Enqueue job (< 1ms)
await env.QUEUE.send({ taskId, action: 'process' });
// Return immediately
return Response.json({ status: 'queued', taskId });
}
// Consumer processes in background
export default {
async queue(batch: MessageBatch<Job>) {
for (const msg of batch.messages) {
await processJob(msg.body); // Can take longer
msg.ack();
}
},
};
Constraint 5: Stateless Execution
Vấn đề
// ❌ Global state doesn't persist
let counter = 0;
export async function GET() {
counter++; // Resets on each cold start
return Response.json({ count: counter });
}
// ❌ In-memory cache doesn't persist
const cache = new Map();
export async function GET(request: Request) {
const key = new URL(request.url).searchParams.get('key');
if (cache.has(key)) return Response.json(cache.get(key));
// Cache lost after worker restarts
}
Giải pháp: External State
// ✅ Use KV for state
export async function GET(request: Request, { env }) {
const key = new URL(request.url).searchParams.get('key');
// Check KV cache
let data = await env.KV.get(key, 'json');
if (!data) {
data = await fetchData(key);
await env.KV.put(key, JSON.stringify(data), {
expirationTtl: 3600, // 1 hour
});
}
return Response.json(data);
}
// ✅ Use Supabase for persistent state
const { data } = await supabase
.from('counters')
.update({ value: sql`value + 1` })
.eq('id', 'main')
.select()
.single();
Package Compatibility Check
Cách kiểm tra package có hoạt động không
# 1. Check package.json của package
# Nếu có "browser" field → likely works
# Nếu có native bindings → won't work
# 2. Search for edge compatibility
npm search [package-name] edge compatible
# 3. Check runtime-compat.unjs.io
# https://runtime-compat.unjs.io/
Packages phổ biến
| Package |
Edge Compatible |
Notes |
@supabase/supabase-js |
✅ |
Full support |
zod |
✅ |
Validation |
dayjs |
✅ |
Date handling |
uuid |
✅ |
UUID generation |
lodash-es |
✅ |
Utilities |
bcrypt |
❌ |
Use bcryptjs |
sharp |
❌ |
Use external service |
pg |
❌ |
Use @neondatabase/serverless |
prisma |
⚠️ |
Use @prisma/client/edge |
Workarounds Summary
| Constraint |
Workaround |
| No fs |
Use KV, R2, or Supabase Storage |
| No native modules |
Use pure JS alternatives or WASM |
| Memory limit |
Streaming, pagination |
| Timeout |
Queue + background jobs |
| Stateless |
KV, Supabase, external DB |
| No Node crypto |
Web Crypto API |
Hands-on Exercise
Kiểm tra compatibility
// Tạo route test các constraints
// app/api/edge-test/route.ts
export const runtime = 'edge';
export async function GET() {
const tests = {
// Web Crypto
crypto: typeof crypto !== 'undefined',
subtle: typeof crypto.subtle !== 'undefined',
// Web APIs
fetch: typeof fetch !== 'undefined',
URL: typeof URL !== 'undefined',
Response: typeof Response !== 'undefined',
// NOT available
fs: typeof require !== 'undefined', // false
process: typeof process !== 'undefined', // partial
};
return Response.json(tests);
}
Tổng kết
Key Takeaways
- Edge = Web Standard APIs only
- No native modules → use pure JS alternatives
- Stateless → use external storage (KV, DB)
- Timeout → use Queue for long tasks
- Memory limit → use streaming
Khi nào cần hybrid approach
┌─────────────────────────────────────────┐
│ Need native module? │
│ │ │
│ ┌────YES───┴───NO────┐ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ Supabase │ │ Edge │ │
│ │ Edge Fn │ │ Runtime │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────┘
Q&A
- Package nào bạn đang dùng có thể không compatible?
- Use case nào cần xử lý file lớn?
- Có concerns gì về timeout limit?