Runtime Limitations
Node.js APIs that break on Cloudflare Workers and how to work around each one.
Compatibility Warning
Workers run V8 isolates, not Node.js. fs, native modules, and many npm packages won't work. Audit your dependencies before migrating.
Cloudflare Workers run V8 isolates, not Node.js. Most Node.js APIs don't exist. Here's what breaks and how to fix it.
The Big Three
1. File System (fs)
// ❌ BREAKS: No filesystem in Workers
import fs from 'fs';
const config = fs.readFileSync('./config.json');
// ✅ WORKS: Import at build time
import config from './config.json';
// ✅ WORKS: Use KV or R2 for runtime data
const config = await env.CONFIG_KV.get('settings', 'json');Any package that reads files at runtime will fail. This includes most config loaders, template engines, and file-based databases.
2. Native Modules
// ❌ BREAKS: Native bindings don't work
import bcrypt from 'bcrypt';
import sharp from 'sharp';
// ✅ WORKS: Pure JS alternatives
import bcryptjs from 'bcryptjs';
// ✅ WORKS: Use Cloudflare's image service
// Or offload to a separate image optimization workerCommon replacements:
| Native Module | Pure JS Alternative |
|---|---|
bcrypt | bcryptjs |
sharp | Cloudflare Images / external service |
canvas | @napi-rs/canvas (limited) |
better-sqlite3 | D1 Database |
pg (native) | @neondatabase/serverless |
3. Node.js Crypto Specifics
// ❌ BREAKS: Node-specific crypto
import crypto from 'crypto';
const hash = crypto.createHash('sha256').update(data).digest('hex');
// ✅ WORKS: Web Crypto API
const hashBuffer = await crypto.subtle.digest('SHA-256',
new TextEncoder().encode(data)
);
const hash = [...new Uint8Array(hashBuffer)]
.map(b => b.toString(16).padStart(2, '0'))
.join('');Streams Differences
Node.js streams and Web streams are different:
// ❌ BREAKS: Node.js streams
import { Readable } from 'stream';
const nodeStream = new Readable();
// ✅ WORKS: Web streams
const webStream = new ReadableStream({
start(controller) {
controller.enqueue('data');
controller.close();
}
});Buffer vs Uint8Array
// ❌ BREAKS: Node.js Buffer
const buf = Buffer.from('hello');
// ✅ WORKS: TextEncoder/Uint8Array
const buf = new TextEncoder().encode('hello');
// ✅ WORKS: Buffer polyfill (if you must)
// Cloudflare provides a Buffer polyfill, but prefer native Web APIsEnvironment Variables
// ❌ BREAKS: process.env at runtime
const apiKey = process.env.API_KEY;
// ✅ WORKS: Accessed via env binding
export default {
async fetch(request, env) {
const apiKey = env.API_KEY;
}
};
// ✅ WORKS: In Next.js pages/API routes
// env is passed through the adapterDatabase Connections
Traditional connection pooling doesn't work:
// ❌ BREAKS: Persistent connections
import { Pool } from 'pg';
const pool = new Pool(); // Connection dies between requests
// ✅ WORKS: HTTP-based database clients
import { neon } from '@neondatabase/serverless';
const sql = neon(env.DATABASE_URL);
// ✅ WORKS: Cloudflare D1
const result = await env.DB.prepare('SELECT * FROM users').all();
// ✅ WORKS: PlanetScale serverless driver
import { connect } from '@planetscale/database';Time and Timers
// ⚠️ CAREFUL: setTimeout has limits
setTimeout(() => {}, 30000); // Max ~30 seconds
// ❌ BREAKS: setInterval in request context
setInterval(() => {}, 1000); // Doesn't persist
// ✅ WORKS: Use Cron Triggers for scheduled work
// wrangler.toml
// [triggers]
// crons = ["*/5 * * * *"]Request/Response Differences
// ❌ BREAKS: Express-style req/res
app.get('/api', (req, res) => {
res.json({ data: true });
});
// ✅ WORKS: Web standard Request/Response
export async function GET(request: Request) {
return Response.json({ data: true });
}The Compatibility Matrix
Check your dependencies against Cloudflare's compatibility:
npx wrangler dev| Error Message | Likely Cause | Fix |
|---|---|---|
X is not defined | Missing polyfill | Add polyfill or replace with Web API |
Cannot find module | Native Node.js module | Replace with edge-compatible alternative |
TypeError: X is not a function | API difference | Check workerd compatibility docs |
Quick Audit Checklist
Before migrating, grep your codebase:
# Find Node.js API usage
grep -r "require('fs')" src/
grep -r "from 'fs'" src/
grep -r "process.env" src/
grep -r "Buffer.from" src/
grep -r "require('crypto')" src/Next: Caching on Cloudflare - How caching works differently and ISR alternatives.