---
name: cloudflare-worker-base
description: |
Set up Cloudflare Workers with Hono routing, Vite plugin, and Static Assets. Prevents 10 errors including export syntax, routing conflicts, Vite 8 nodejs_compat, base option regression, and cache corruption.
Use when: creating Workers projects, configuring Hono/Vite, or troubleshooting export syntax, API route conflicts, HMR issues, or Vite 8+ compatibility.
user-invocable: true
---
# Cloudflare Worker Base Stack
**Production-tested**: cloudflare-worker-base-test (https://cloudflare-worker-base-test.webfonts.workers.dev)
**Last Updated**: 2026-01-20
**Status**: Production Ready ✅
**Latest Versions**: hono@4.11.3, @cloudflare/vite-plugin@1.17.1, vite@7.3.1, wrangler@4.54.0
**Skill Version**: 3.1.0
**Recent Updates (2025-2026)**:
- **Wrangler 4.55+**: Auto-config for frameworks (`wrangler deploy --x-autoconfig`)
- **Wrangler 4.45+**: Auto-provisioning for R2, D1, KV bindings (enabled by default)
- **Workers RPC**: JavaScript-native RPC via `WorkerEntrypoint` class for service bindings
- **March 2025**: Wrangler v4 release (minimal breaking changes, v3 supported until Q1 2027)
- **June 2025**: Native Integrations removed from dashboard (CLI-based approach with wrangler secrets)
- **2025 Platform**: Workers VPC Services, Durable Objects Data Studio, 64 env vars (5KB each), unlimited Cron Triggers per Worker, WebSocket 32 MiB messages, node:fs/Web File System APIs
- **2025 Static Assets**: Gradual rollout asset mismatch issue, free tier 429 errors with run_worker_first, Vite plugin auto-detection
- **Hono 4.11.x**: Enhanced TypeScript RPC type inference, cloneRawRequest utility, JWT aud validation, auth middleware improvements
---
## Quick Start (5 Minutes)
```bash
# 1. Scaffold project
npm create cloudflare@latest my-worker -- --type hello-world --ts --git --deploy false --framework none
# 2. Install dependencies
cd my-worker
npm install hono@4.11.3
npm install -D @cloudflare/vite-plugin@1.17.1 vite@7.3.1
# 3. Create wrangler.jsonc
{
"name": "my-worker",
"main": "src/index.ts",
"account_id": "YOUR_ACCOUNT_ID",
"compatibility_date": "2025-11-11",
"assets": {
"directory": "./public/",
"binding": "ASSETS",
"not_found_handling": "single-page-application",
"run_worker_first": ["/api/*"] // CRITICAL: Prevents SPA fallback from intercepting API routes
}
}
# 4. Create vite.config.ts
import { defineConfig } from 'vite'
import { cloudflare } from '@cloudflare/vite-plugin'
export default defineConfig({ plugins: [cloudflare()] })
# 5. Create src/index.ts
import { Hono } from 'hono'
type Bindings = { ASSETS: Fetcher }
const app = new Hono<{ Bindings: Bindings }>()
app.get('/api/hello', (c) => c.json({ message: 'Hello!' }))
app.all('*', (c) => c.env.ASSETS.fetch(c.req.raw))
export default app // CRITICAL: Use this pattern (NOT { fetch: app.fetch })
# 6. Deploy
npm run dev # Local: http://localhost:8787
wrangler deploy # Production
```
**Critical Configuration**:
- `run_worker_first: ["/api/*"]` - Without this, SPA fallback intercepts API routes returning `index.html` instead of JSON ([workers-sdk #8879](https://github.com/cloudflare/workers-sdk/issues/8879))
- `export default app` - Using `{ fetch: app.fetch }` causes "Cannot read properties of undefined" ([honojs/hono #3955](https://github.com/honojs/hono/issues/3955))
## Known Issues Prevention
This skill prevents **10 documented issues**:
### Issue #1: Export Syntax Error
**Error**: "Cannot read properties of undefined (reading 'map')"
**Source**: [honojs/hono #3955](https://github.com/honojs/hono/issues/3955)
**Prevention**: Use `export default app` (NOT `{ fetch: app.fetch }`)
### Issue #2: Static Assets Routing Conflicts
**Error**: API routes return `index.html` instead of JSON
**Source**: [workers-sdk #8879](https://github.com/cloudflare/workers-sdk/issues/8879)
**Prevention**: Add `"run_worker_first": ["/api/*"]` to wrangler.jsonc
### Issue #3: Scheduled/Cron Not Exported
**Error**: "Handler does not export a scheduled() function"
**Source**: [honojs/vite-plugins #275](https://github.com/honojs/vite-plugins/issues/275)
**Prevention**: Use Module Worker format when needed:
```typescript
export default {
fetch: app.fetch,
scheduled: async (event, env, ctx) => { /* ... */ }
}
```
### Issue #4: HMR Race Condition
**Error**: "A hanging Promise was canceled" during development
**Source**: [workers-sdk #9518](https://github.com/cloudflare/workers-sdk/issues/9518)
**Prevention**: Use `@cloudflare/vite-plugin@1.13.13` or later
### Issue #5: Static Assets Upload Race
**Error**: Non-deterministic deployment failures in CI/CD
**Source**: [workers-sdk #7555](https://github.com/cloudflare/workers-sdk/issues/7555)
**Prevention**: Use Wrangler 4.x+ with retry logic (fixed in recent versions)
### Issue #6: Service Worker Format Confusion
**Error**: Using deprecated Service Worker format
**Source**: Cloudflare migration guide
**Prevention**: Always use ES Module format
### Issue #7: Gradual Rollouts Asset Mismatch (2025)
**Error**: 404 errors for fingerprinted assets during gradual deployments
**Source**: [Cloudflare Static Assets Docs](https://developers.cloudflare.com/workers/static-assets/routing/advanced/gradual-rollouts)
**Why It Happens**: Modern frameworks (React/Vue/Angular with Vite) generate fingerprinted filenames (e.g., `index-a1b2c3d4.js`). During gradual rollouts between versions, a user's initial request may go to Version A (HTML references `index-a1b2c3d4.js`), but subsequent asset requests route to Version B (only has `index-m3n4o5p6.js`), causing 404s
**Prevention**:
- Avoid gradual deployments with fingerprinted assets
- Use instant cutover deployments for static sites
- Or implement version-aware routing with custom logic
### Issue #8: Free Tier 429 Errors with run_worker_first (2025)
**Error**: 429 (Too Many Requests) responses on asset requests when exceeding free tier limits
**Source**: [Cloudflare Static Assets Billing Docs](https://developers.cloudflare.com/workers/static-assets/billing-and-limitations)
**Why It Happens**: When using `run_worker_first`, requests matching specified patterns ALWAYS invoke your Worker script (counted toward free tier limits). After exceeding limits, these requests receive 429 instead of falling back to free static asset serving
**Prevention**:
- Upgrade to Workers Paid plan ($5/month) for unlimited requests
- Use negative patterns (`!/pattern`) to exclude paths from Worker invocation
- Minimize `run_worker_first` patterns to only essential API routes
### Issue #9: Vite 8 Breaks nodejs_compat with require()
**Error**: `Calling require for "buffer" in an environment that doesn't expose the require function`
**Source**: [workers-sdk #11948](https://github.com/cloudflare/workers-sdk/issues/11948)
**Affected Versions**: Vite 8.x with @cloudflare/vite-plugin 1.21.0+
**Why It Happens**: Vite 8 uses Rolldown bundler which doesn't convert `require()` to `import` for external modules. Workers don't expose `require()` function, causing Node built-in module imports to fail at runtime.
**Prevention**:
```typescript
// vite.config.ts - Add esmExternalRequirePlugin
import { defineConfig } from 'vite'
import { cloudflare } from '@cloudflare/vite-plugin'
import { esmExternalRequirePlugin } from 'vite'
import { builtinModules } from 'node:module'
export default defineConfig({
plugins: [
cloudflare(),
esmExternalRequirePlugin({
external: [/^node:/, ...builtinModules],
}),
],
})
```
**Status**: Workaround available. Vite team working on fix ([vitejs/vite#21452](https://github.com/vitejs/vite/pull/21452)).
### Issue #10: Vite base Option Breaks SPA Routing (1.13.8+)
**Error**: `curl http://localhost:5173/prefix` returns 404 instead of index.html
**Source**: [workers-sdk #11857](https://github.com/cloudflare/workers-sdk/issues/11857)
**Affected Versions**: @cloudflare/vite-plugin 1.13.8+
**Why It Happens**: Plugin now passes full URL with base path to Asset Worker (matching prod behavior). Platform support for `assets.base` not yet available.
**Prevention** (dev-mode workaround):
```typescript
// worker.ts - Strip base path in development
if (import.meta.env.DEV) {
url.pathname = url.pathname.replace(import.meta.env.BASE_URL, '');
if (url.pathname === '/') {
return this.env.ASSETS.fetch(request);
}
request = new Request(url, request);
}
```
**Status**: Intentional change to align dev with prod. Platform feature `assets.base` planned for Q1 2026 ([workers-sdk #9885](https://github.com/cloudflare/workers-sdk/issues/9885)).
## Route Priority with run_worker_first
**Critical Understanding**: `"not_found_handling": "single-page-application"` returns `index.html` for unknown routes (enables React Router, Vue Router). Without `run_worker_first`, this intercepts API routes!
**Request Routing with `run_worker_first: ["/api/*"]`**:
1. `/api/hello` → Worker handles (returns JSON)
2. `/` → Static Assets serve `index.html`
3. `/styles.css` → Static Assets serve `styles.css`
4. `/unknown` → Static Assets serve `index.html` (SPA fallback)
**Static Assets Caching**: Automatic edge caching. Cache bust with query strings: ``
**Free Tier Warning** (2025): `run_worker_first` patterns count toward free tier limits. After exceeding, requests get 429 instead of falling back to free static assets. Use negative patterns (`!/pattern`) or upgrade to Paid plan.
## Auto-Provisioning (Wrangler 4.45+)
**Default Behavior**: Wrangler automatically provisions R2 buckets, D1 databases, and KV namespaces when deploying. This eliminates manual resource creation steps.
**Critical: Always Specify Resource Names**
⚠️ **Edge Case** ([workers-sdk #11870](https://github.com/cloudflare/workers-sdk/issues/11870)): If you provide only `binding` without `database_name`/`bucket_name`, Wrangler uses the binding name as the resource name. This causes confusing behavior with `wrangler dev` and subcommands, which prefer `database_id` → `database_name` → `binding`.
```jsonc
// ❌ DON'T: Binding-only creates database named "DB"
{
"d1_databases": [{ "binding": "DB" }]
}
// ✅ DO: Explicit names prevent confusion
{
"d1_databases": [
{
"binding": "DB",
"database_name": "my-app-db" // Always specify!
}
],
"r2_buckets": [
{
"binding": "STORAGE",
"bucket_name": "my-app-files" // Always specify!
}
],
"kv_namespaces": [
{
"binding": "CACHE",
"title": "my-app-cache" // Always specify!
}
]
}
```
```bash
# Deploy - resources auto-provisioned if they don't exist
wrangler deploy
# Disable auto-provisioning (use existing resources only)
wrangler deploy --no-x-provision
```
**Benefits**:
- No separate `wrangler d1 create` / `wrangler r2 create` steps needed
- Idempotent - existing resources are used, not recreated
- Works with local dev (`wrangler dev` creates local emulated resources)
## Workers RPC (Service Bindings)
**What It Is**: JavaScript-native RPC system for calling methods between Workers. Uses Cap'n Proto under the hood for zero-copy message passing.
**Use Case**: Split your application into multiple Workers (e.g., API Worker + Auth Worker + Email Worker) that call each other with type-safe methods.
**Defining an RPC Service**:
```typescript
import { WorkerEntrypoint } from 'cloudflare:workers'
export class AuthService extends WorkerEntrypoint {
async verifyToken(token: string): Promise<{ userId: string; valid: boolean }> {
// Access bindings via this.env
const session = await this.env.SESSIONS.get(token)
return session ? { userId: session.userId, valid: true } : { userId: '', valid: false }
}
async createSession(userId: string): Promise {
const token = crypto.randomUUID()
await this.env.SESSIONS.put(token, JSON.stringify({ userId }), { expirationTtl: 3600 })
return token
}
}
// Default export still handles HTTP requests
export default { fetch: ... }
```
**Calling from Another Worker**:
```typescript
// wrangler.jsonc
{
"services": [
{ "binding": "AUTH", "service": "auth-worker", "entrypoint": "AuthService" }
]
}
// In your main Worker
const { valid, userId } = await env.AUTH.verifyToken(authHeader)
```
**Key Points**:
- **Zero latency**: Workers on same account typically run in same thread
- **Type-safe**: Full TypeScript support for method signatures
- **32 MiB limit**: Max serialized RPC message size
- **Self-bindings**: In `wrangler dev`, shows as `[connected]` for same-Worker calls
## Troubleshooting
### Random "Response Already Sent" Errors in Development
**Symptom**: Sporadic `ResponseSentError: The response has already been sent to the browser and cannot be altered` during `wrangler dev`
**Source**: [workers-sdk #11932](https://github.com/cloudflare/workers-sdk/issues/11932)
**Cause**: Cache corruption in `.wrangler` directory or stale Vite cache
**Solution**:
```bash
# Clear all caches
rm -rf .wrangler dist node_modules/.vite
# Rebuild
npm run build
# Recreate local D1 databases if needed
wrangler d1 execute DB --local --file schema.sql
```
**Applies to**: Wrangler 4.x full Workers mode (not Cloudflare Pages)
## Community Tips
> **Note**: These tips come from community discussions. Verify against your version.
### Avoid vite-tsconfig-paths v6 with React SSR
**Source**: [workers-sdk #11825](https://github.com/cloudflare/workers-sdk/issues/11825) (Community-sourced)
If using React SSR with `@cloudflare/vite-plugin`, pin to `vite-tsconfig-paths@5.1.4`:
```bash
npm install vite-tsconfig-paths@5.1.4
```
**Why**: Version 6.x doesn't follow path aliases during dependency scan, causing duplicate React instances and "Invalid hook call" errors.
**Applies to**: Projects using TypeScript path aliases with React SSR
### Use crypto.randomUUID() Instead of uuid Package
**Source**: [workers-sdk #11957](https://github.com/cloudflare/workers-sdk/issues/11957)
The `uuid@11` package (ESM) can cause runtime crashes with "fileURLToPath undefined" in Workers. Use the native Web Crypto API instead:
```typescript
// ✅ Native Workers API (recommended)
const uuid = crypto.randomUUID();
// ❌ Avoid uuid package in Workers
import { v4 as uuidv4 } from 'uuid'; // May crash
```
**Applies to**: All Workers projects needing UUIDs
## Commands
### `/deploy` - One-Command Deploy Pipeline
**Use when**: Ready to commit, push, and deploy your Cloudflare Worker in one step.
**Does**:
1. **Pre-flight**: Verifies wrangler config, checks for changes, builds, runs TypeScript check
2. **Commit & Push**: Stages changes, generates conventional commit message, pushes to remote
3. **Deploy**: Runs `wrangler deploy`, captures Worker URL
4. **Report**: Shows commit hash, branch, deployed URL, any warnings
**Time savings**: 2-3 min per deploy cycle
**Edge Cases Handled**:
- No changes to commit → skips to deploy
- Build script missing → warns and continues
- No remote configured → reports error with suggestion
- TypeScript errors → stops and reports
---
## Bundled Resources
**Templates**: Complete setup files in `templates/` directory (wrangler.jsonc, vite.config.ts, package.json, tsconfig.json, src/index.ts, public/index.html, styles.css, script.js)
## Official Documentation
- **Cloudflare Workers**: https://developers.cloudflare.com/workers/
- **Static Assets**: https://developers.cloudflare.com/workers/static-assets/
- **Vite Plugin**: https://developers.cloudflare.com/workers/vite-plugin/
- **Wrangler**: https://developers.cloudflare.com/workers/wrangler/
- **Hono**: https://hono.dev/docs/getting-started/cloudflare-workers
- **MCP Tool**: Use `mcp__cloudflare-docs__search_cloudflare_documentation` for latest docs
---
## Dependencies (Latest Verified 2026-01-03)
```json
{
"dependencies": {
"hono": "^4.11.3"
},
"devDependencies": {
"@cloudflare/vite-plugin": "^1.17.1",
"@cloudflare/workers-types": "^4.20260103.0",
"vite": "^7.3.1",
"wrangler": "^4.54.0",
"typescript": "^5.9.3"
}
}
```
---
## Production Validation
**Live Example**: https://cloudflare-worker-base-test.webfonts.workers.dev (build time: 45 min, 0 errors, all 10 issues prevented)
---
**Last verified**: 2026-01-20 | **Skill version**: 3.1.0 | **Changes**: Added Vite 8 nodejs_compat workaround (Issue #9), Vite base option regression (Issue #10), auto-provisioning edge case warning, troubleshooting section for cache corruption, and community tips for vite-tsconfig-paths v6 and uuid package alternatives. Research conducted by skill-researcher agent covering post-May 2025 Workers SDK updates.