Cloudflare Platform
Understanding the Cloudflare runtime model is essential before picking a framework. The platform has quirks that eliminate some frameworks and favor others. For a deeper look at the runtime, see the Platform Model concept in the Cloudflare platform topic.
Workers vs Pages
Cloudflare has two deployment targets. Pages is being phased out.
| Workers | Pages | |
|---|---|---|
| Status | Active, recommended | Legacy, maintenance mode |
| Static assets | Workers Static Assets | Pages Functions |
| Server code | Full Worker entry point | Limited Functions |
| Bindings | All (KV, D1, R2, DO, Queues, AI) | Subset |
| Config | wrangler.jsonc or wrangler.toml | Dashboard or wrangler.toml |
Gotcha: New projects should target Workers, not Pages. Astro’s adapter v13 dropped Pages support entirely. Cloudflare’s framework guides now default to Workers.
The Runtime: workerd
Cloudflare Workers run on workerd, not Node.js. Key differences:
// These Node.js APIs don't exist in workerd:
// - fs, path, child_process, net, os
// - process.env (use env bindings instead)
// - __dirname, __filename
// - require() (ESM only)
// These DO work:
// - fetch, Request, Response, Headers
// - URL, URLSearchParams
// - crypto (Web Crypto API)
// - TextEncoder, TextDecoder
// - setTimeout, setInterval (limited)
// - console.log
The nodejs_compat flag enables polyfills for some Node.js APIs (Buffer, crypto, stream, etc.), but filesystem and process APIs remain unavailable.
// wrangler.jsonc
{
"compatibility_flags": ["nodejs_compat"]
}
Bindings
Bindings are how Workers access Cloudflare services. They’re injected at runtime, not imported.
// In a Worker entry point
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// env contains your bindings
const value = await env.MY_KV.get("key");
const result = await env.MY_DB.prepare("SELECT * FROM users").all();
const object = await env.MY_BUCKET.get("file.png");
return new Response(value);
}
};
Common binding types:
| Binding | Config key | Use for |
|---|---|---|
| KV | kv_namespaces | Key-value storage |
| D1 | d1_databases | SQL database |
| R2 | r2_buckets | Object storage |
| Durable Objects | durable_objects | Stateful coordination (deep-dive) |
| Queues | queues | Async message processing |
| AI | ai | Inference (Workers AI) |
| Vectorize | vectorize | Vector search |
| Service Bindings | services | Worker-to-Worker calls |
Accessing Bindings in Frameworks
Each framework has its own pattern for accessing bindings:
// React Router v7 - loader/action context
export async function loader({ context }: LoaderFunctionArgs) {
const env = context.cloudflare.env;
return await env.MY_KV.get("key");
}
// Astro - module import (v13+)
import { env } from "cloudflare:workers";
const value = await env.MY_KV.get("key");
// SvelteKit - platform object
export async function load({ platform }) {
return await platform.env.MY_KV.get("key");
}
// Hono - context
app.get("/", async (c) => {
const value = await c.env.MY_KV.get("key");
return c.text(value);
});
// Next.js (OpenNext) - getRequestContext
import { getRequestContext } from "@opennextjs/cloudflare";
const { env } = getRequestContext();
The Cloudflare Vite Plugin
The @cloudflare/vite-plugin is Cloudflare’s answer to the dev/prod parity problem. Without it, vite dev runs your server code in Node.js, but production runs in workerd. Bugs only show up after deploying.
flowchart LR
subgraph "Without CF Vite Plugin"
A[vite dev] -->|Node.js| B[Your server code]
C[wrangler deploy] -->|workerd| D[Your server code]
end
flowchart LR
subgraph "With CF Vite Plugin"
E[vite dev] -->|workerd via Vite Environment API| F[Your server code]
G[wrangler deploy] -->|workerd| H[Your server code]
end
The plugin uses Vite’s Environment API to run server-side code inside a local workerd instance during development. Bindings, runtime APIs, and compatibility flags all behave identically in dev and prod.
Which Frameworks Use It
| Framework | CF Vite Plugin | Dev runtime |
|---|---|---|
| React Router v7 | Yes | workerd |
| TanStack Start | Yes | workerd |
| Astro (adapter v13) | Yes | workerd |
| Vite + React SPA | Yes | workerd (for assets) |
| SvelteKit | No | Node.js |
| Nuxt | No | Node.js |
| Next.js (OpenNext) | No | Node.js |
| Hono | Optional | workerd if configured |
Wrangler Auto-Detection
Running npx wrangler deploy in a framework project without a config file triggers auto-detection:
- Detects the framework from
package.json - Generates appropriate
wrangler.jsonc - Installs required adapters
- Builds and deploys
Supported frameworks for auto-detection: React Router v7, Astro, SvelteKit, Nuxt, Qwik, SolidStart, and Vite + React/Vue SPAs.
Gotcha: Auto-detection doesn’t work for Next.js (needs OpenNext setup) or Hono (already a native Worker).
Resource Limits
Workers have hard limits that affect framework choice:
| Resource | Free | Paid |
|---|---|---|
| CPU time per request | 10ms | 30s (up to 5min via limits.cpu_ms) |
| Memory | 128MB | 128MB |
| Script size (compressed) | 3MB | 10MB |
| Subrequests per request | 50 | 10,000 |
| Environment variables | 64 | 128 |
Gotcha: The 3MB free-tier script size limit (compressed) can be tight for frameworks with large server bundles (Nuxt, Next.js). Check your bundle size with
wrangler deploy --dry-run.