Bỏ qua

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

  1. Edge = Web Standard APIs only
  2. No native modules → use pure JS alternatives
  3. Stateless → use external storage (KV, DB)
  4. Timeout → use Queue for long tasks
  5. Memory limit → use streaming

Khi nào cần hybrid approach

┌─────────────────────────────────────────┐
│           Need native module?            │
│                    │                     │
│         ┌────YES───┴───NO────┐          │
│         ▼                    ▼          │
│   ┌──────────┐       ┌──────────┐       │
│   │ Supabase │       │  Edge    │       │
│   │ Edge Fn  │       │ Runtime  │       │
│   └──────────┘       └──────────┘       │
└─────────────────────────────────────────┘

Q&A

  1. Package nào bạn đang dùng có thể không compatible?
  2. Use case nào cần xử lý file lớn?
  3. Có concerns gì về timeout limit?