Table of Contents
🌏 中文版
Cloudflare Workers is often compared to AWS Lambda, but the two have fundamentally different architectures. Lambda runs in containers; Workers runs in V8 Isolates. That single difference shapes everything.
V8 Isolates vs. Containers
Lambda's cold start problem is essentially the cost of container startup: pulling an image, allocating resources, booting the runtime — best case a few hundred milliseconds, worst case several seconds.
Workers uses V8 Isolates — the same engine that runs JavaScript in Chrome. Isolates are memory-isolated from each other but share the same V8 engine, so there's no new process or container to spin up. When Cloudflare claims "no cold starts," it is not marketing but a consequence of the architecture — the official phrasing is that this model "eliminates the cold starts of the virtual machine model" and is "around a hundred times faster than a Node process". (The widely repeated "0–5ms startup" figure has no traceable source on the current docs, so it is not cited here. Note too that the limits page imposes a 1 second startup-time ceiling for global scope.)
The other key difference is where code runs. Lambda executes in the AWS region you choose; Workers is automatically deployed across Cloudflare's global network — described officially as thousands of machines spread across hundreds of locations. A request from Taiwan gets handled at a nearby edge node — no round-tripping across the globe.
Limitations Up Front
Workers isn't a silver bullet — but the constraint that actually stops you is not the one most people expect. The limit is CPU time, not wall-clock time.
- CPU time: counts only time the CPU spends executing your code. Waiting on
fetch()or a D1 query does not count. 10ms on the free plan; on paid, the default is 30 seconds and can be raised to a 5-minute ceiling in configuration - Wall time: an incoming HTTP request has no hard limit while the client stays connected;
waitUntil()extends execution for a while after the response is sent. Cron Triggers, Queue consumers, and Durable Object alarms are the invocation types with minute-scale wall-time caps - Memory: 128 MB per Worker, identical on both plans
- Worker size: free and paid have different bundle-size ceilings; an over-fat bundle fails to deploy
- Subrequests: there is a cap on outbound requests per invocation, much tighter on free; paid can raise it in configuration
- No full native Node.js API surface:
fs,child_processand friends are out, though most pure-JS packages work fine
The exact numbers move. Workers Limits is the only source worth trusting — it now carries separate tables for CPU time and wall time, and is worth reading end to end.
The Workers runtime is not full Node.js. Built-ins like node:crypto and node:buffer come through the Node.js compatibility layer and require the nodejs_compat compatibility flag.
The Simplest Possible Worker
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/") {
return new Response("Hello from the edge", { status: 200 });
}
return new Response("Not found", { status: 404 });
},
};
env is where Bindings come in — more on that below.
Building an API with Hono
Writing a full API with raw fetch handlers gets tedious fast — you end up rolling your own routing. Hono is a Web framework designed specifically for edge runtimes: tiny bundle size, native support for the Workers API.
import { Hono } from "hono";
import { cors } from "hono/cors";
type Bindings = {
DB: D1Database;
KV: KVNamespace;
AI: Ai;
};
const app = new Hono<{ Bindings: Bindings }>();
app.use("*", cors());
app.get("/api/posts", async (c) => {
const posts = await c.env.DB.prepare(
"SELECT id, title, created_at FROM posts ORDER BY created_at DESC LIMIT 20"
).all();
return c.json(posts.results);
});
app.post("/api/posts", async (c) => {
const body = await c.req.json<{ title: string; content: string }>();
const result = await c.env.DB.prepare(
"INSERT INTO posts (title, content) VALUES (?, ?)"
)
.bind(body.title, body.content)
.run();
return c.json({ id: result.meta.last_row_id }, 201);
});
export default app;
c.env gives you type-safe access to your Workers Bindings, with full IDE autocomplete.
The wrangler CLI
wrangler is Cloudflare's official developer tool, covering everything from project initialization to production deployment.
Start new projects with npm create cloudflare@latest — it installs wrangler as a project devDependency and scaffolds the config. Invoke it day to day as npx wrangler <command>; a global install is discouraged because the version then drifts from the project. The commands you will actually use are wrangler login, wrangler dev (run locally), wrangler deploy, and wrangler tail (live logs). Full list: Wrangler commands.
wrangler dev runs a real Workers runtime locally. Bindings (D1, KV, R2) default to local emulation and can be pointed at real Cloudflare resources instead.
The configuration file now comes in JSON and TOML flavours, and Cloudflare recommends wrangler.jsonc for new projects — some newer Wrangler features are JSON-only:
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "my-api",
"main": "src/index.ts",
// Set this to the date you started the Worker
"compatibility_date": "2026-08-18",
"compatibility_flags": ["nodejs_compat"],
"d1_databases": [
{ "binding": "DB", "database_name": "my-db", "database_id": "<DATABASE_ID>" }
],
"kv_namespaces": [{ "binding": "KV", "id": "<NAMESPACE_ID>" }],
"ai": { "binding": "AI" },
"observability": { "enabled": true }
}
wrangler.toml still works — the schema is identical, only the syntax differs. Field reference: Wrangler configuration.
The Bindings System
Bindings are how Workers connects to Cloudflare services. They're injected via the env object — no API keys required, no network overhead (inside a Worker, a Binding is a direct runtime connection).
Key Bindings:
| Binding | Purpose |
|---|---|
D1Database | SQLite relational database |
KVNamespace | Key-value store, ideal for caching |
R2Bucket | Object storage, S3-compatible |
Ai | Workers AI — embeddings and LLMs |
Queue | Message queue for async job processing |
DurableObjectNamespace | Stateful single-instance objects |
Fetcher | Service binding to call another Worker |
Cloudflare KV is eventually consistent — global reads are fast but writes have propagation delay. Cloudflare R2 is the right choice for static assets and user-uploaded media, with no egress fees.
The Billing Model
Rate cards rot, so here is only the shape: Workers bills on two axes — request count and CPU milliseconds. Time spent waiting on network I/O is not billed, and that is the biggest structural difference from Lambda (billed in GB-seconds, waiting included). A Worker that mostly calls external APIs and computes little is disproportionately cheap here.
The free plan has a daily request cap; the paid plan has monthly included allowances with usage-based overage. D1, KV, R2, and Workers AI each carry their own free allowance and their own billing axes, counted separately. Current numbers: Workers Pricing.
The free plan covers most side projects and small-to-medium applications.
How NobodyClimb Uses Workers
NobodyClimb runs its entire backend on Workers with no traditional server:
- Web SSR: Next.js 15 deployed to Workers via @opennextjs/cloudflare — dynamic routes handled by Workers, static assets served via Cloudflare Assets
- API: A Hono Worker connected to D1 (user data, climbing records) and KV (caching, rate limiting)
- AI: The RAG Pipeline also runs in Workers — embeddings and LLM calls go through
env.AIvia Workers AI
The core reasoning: a climbing community platform has unpredictable traffic. Running a persistent server wastes money when traffic is low. Workers bills per request — idle time costs nothing, and when traffic spikes, it responds fast from the nearest edge node worldwide.
When to Use Workers — and When Not To
Good fits for Workers:
- REST APIs, GraphQL endpoints
- SSR with Next.js, Nuxt, or similar adapters
- Edge middleware (auth, redirects, A/B testing)
- Webhook handlers
- Lightweight scheduled tasks (via Cron Triggers)
Poor fits for Workers:
- Heavy CPU-bound workloads (ML training, image or video transcoding) — the hardest constraint; the CPU ceiling stops you outright
- Anything whose working set exceeds 128 MB of memory
- Packages that depend on native Node.js addons (some database drivers,
.nodebinaries) - Anything requiring a local filesystem (
fsis unavailable)
Two things are no longer reasons to avoid Workers: long-lived WebSocket connections have Durable Objects (no wall-time cap while the caller stays connected), and long-running processes have Workflows. They are a different mental model, not a capability Workers lacks.
Compared to traditional VPS or container setups: what Workers gives up is how much CPU and memory a single request may burn. What you get back is zero infrastructure management, global deployment, and billing that charges only for actual computation. Small units of work belong on Workers; large ones belong in containers.
Changelog
- 2026-08-19: Fact-checked against primary sources and refreshed; perishable details handed back to official docs. Added to the "Cloudflare Edge Stack" series.
References
Loading...