🎯

cloudflare-worker-base

🎯Skill

from ovachiever/droid-tings

VibeIndex|
What it does

Scaffolds Cloudflare Workers with Hono routing, Vite plugin, and Static Assets, preventing common deployment and configuration errors.

πŸ“¦

Part of

ovachiever/droid-tings(370 items)

cloudflare-worker-base

Installation

npm runRun npm script
npm run dev
πŸ“– Extracted from docs: ovachiever/droid-tings
15Installs
20
-
AddedFeb 4, 2026

Skill Details

SKILL.md

|

Overview

# Cloudflare Worker Base Stack

Production-tested: cloudflare-worker-base-test (https://cloudflare-worker-base-test.webfonts.workers.dev)

Last Updated: 2025-10-20

Status: Production Ready βœ…

---

Quick Start (5 Minutes)

1. Scaffold Project

```bash

npm create cloudflare@latest my-worker -- \

--type hello-world \

--ts \

--git \

--deploy false \

--framework none

```

Why these flags:

  • --type hello-world: Clean starting point
  • --ts: TypeScript support
  • --git: Initialize git repo
  • --deploy false: Don't deploy yet (configure first)
  • --framework none: We'll add Vite ourselves

2. Install Dependencies

```bash

cd my-worker

npm install hono@4.10.1

npm install -D @cloudflare/vite-plugin@1.13.13 vite@^7.0.0

```

Version Notes:

  • hono@4.10.1: Latest stable (verified 2025-10-20)
  • @cloudflare/vite-plugin@1.13.13: Latest stable, fixes HMR race condition
  • vite: Latest version compatible with Cloudflare plugin

3. Configure Wrangler

Create or update wrangler.jsonc:

```jsonc

{

"$schema": "node_modules/wrangler/config-schema.json",

"name": "my-worker",

"main": "src/index.ts",

"account_id": "YOUR_ACCOUNT_ID", // Find this in your Cloudflare dashboard (Workers & Pages -> Overview).

"compatibility_date": "2025-10-11",

"observability": {

"enabled": true

},

"assets": {

"directory": "./public/",

"binding": "ASSETS",

"not_found_handling": "single-page-application",

"run_worker_first": ["/api/*"]

}

}

```

CRITICAL: run_worker_first Configuration

  • Without this, SPA fallback intercepts API routes
  • API routes return index.html instead of JSON
  • Source: [workers-sdk #8879](https://github.com/cloudflare/workers-sdk/issues/8879)

4. Configure Vite

Create vite.config.ts:

```typescript

import { defineConfig } from 'vite'

import { cloudflare } from '@cloudflare/vite-plugin'

export default defineConfig({

plugins: [

cloudflare({

// Optional: Configure the plugin if needed

}),

],

})

```

Why @cloudflare/vite-plugin:

  • Official plugin from Cloudflare
  • Supports HMR with Workers
  • Enables local development with Miniflare
  • Version 1.13.13 fixes "A hanging Promise was canceled" error

---

The Four-Step Setup Process

Step 1: Create Hono App with API Routes

Create src/index.ts:

```typescript

/**

* Cloudflare Worker with Hono

*

* CRITICAL: Export pattern to prevent build errors

* βœ… CORRECT: export default app

* ❌ WRONG: export default { fetch: app.fetch }

*/

import { Hono } from 'hono'

// Type-safe environment bindings

type Bindings = {

ASSETS: Fetcher

}

const app = new Hono<{ Bindings: Bindings }>()

/**

* API Routes

* Handled BEFORE static assets due to run_worker_first config

*/

app.get('/api/hello', (c) => {

return c.json({

message: 'Hello from Cloudflare Workers!',

timestamp: new Date().toISOString(),

})

})

app.get('/api/health', (c) => {

return c.json({

status: 'ok',

version: '1.0.0',

environment: c.env ? 'production' : 'development',

})

})

/**

* Fallback to Static Assets

* Any route not matched above is served from public/ directory

*/

app.all('*', (c) => {

return c.env.ASSETS.fetch(c.req.raw)

})

/**

* Export the Hono app directly (ES Module format)

* This is the correct pattern for Cloudflare Workers with Hono + Vite

*/

export default app

```

Why This Export Pattern:

  • Source: [honojs/hono #3955](https://github.com/honojs/hono/issues/3955)
  • Using { fetch: app.fetch } causes: "Cannot read properties of undefined (reading 'map')"
  • Exception: If you need scheduled/tail handlers, use Module Worker format:

```typescript

export default {

fetch: app.fetch,

scheduled: async (event, env, ctx) => { / ... / }

}

```

Step 2: Create Static Frontend

Create public/index.html:

```html

My Worker App

Cloudflare Worker + Static Assets

```

Create public/script.js:

```javascript

async function testAPI() {

const response = await fetch('/api/hello')

const data = await response.json()

document.getElementById('output').textContent = JSON.stringify(data, null, 2)

}

```

Create public/styles.css:

```css

body {

font-family: system-ui, -apple-system, sans-serif;

max-width: 800px;

margin: 40px auto;

padding: 20px;

}

button {

background: #0070f3;

color: white;

border: none;

padding: 12px 24px;

border-radius: 6px;

cursor: pointer;

}

pre {

background: #f5f5f5;

padding: 16px;

border-radius: 6px;

overflow-x: auto;

}

```

Step 3: Update Package Scripts

Update package.json:

```json

{

"scripts": {

"dev": "wrangler dev",

"deploy": "wrangler deploy",

"cf-typegen": "wrangler types"

}

}

```

Step 4: Test & Deploy

```bash

# Generate TypeScript types for bindings

npm run cf-typegen

# Start local dev server (http://localhost:8787)

npm run dev

# Deploy to production

npm run deploy

```

---

Known Issues Prevention

This skill prevents 6 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 (shown in Step 1)

---

Configuration Files Reference

wrangler.jsonc (Full Example)

```jsonc

{

"$schema": "node_modules/wrangler/config-schema.json",

"name": "my-worker",

"main": "src/index.ts",

"account_id": "YOUR_ACCOUNT_ID", // Find this in your Cloudflare dashboard (Workers & Pages -> Overview).

"compatibility_date": "2025-10-11",

"observability": {

"enabled": true

},

"assets": {

"directory": "./public/",

"binding": "ASSETS",

"not_found_handling": "single-page-application",

"run_worker_first": ["/api/*"]

}

/ Optional: Environment Variables /

// "vars": { "MY_VARIABLE": "production_value" }

/ Optional: KV Namespace Bindings /

// "kv_namespaces": [

// { "binding": "MY_KV", "id": "YOUR_KV_ID" }

// ]

/ Optional: D1 Database Bindings /

// "d1_databases": [

// { "binding": "DB", "database_name": "my-db", "database_id": "YOUR_DB_ID" }

// ]

/ Optional: R2 Bucket Bindings /

// "r2_buckets": [

// { "binding": "MY_BUCKET", "bucket_name": "my-bucket" }

// ]

}

```

Why wrangler.jsonc over wrangler.toml:

  • JSON format preferred since Wrangler v3.91.0
  • Better IDE support with JSON schema
  • Comments allowed with JSONC

vite.config.ts (Full Example)

```typescript

import { defineConfig } from 'vite'

import { cloudflare } from '@cloudflare/vite-plugin'

export default defineConfig({

plugins: [

cloudflare({

// Persist state between HMR updates

persist: true,

}),

],

// Optional: Configure server

server: {

port: 8787,

},

// Optional: Build optimizations

build: {

target: 'esnext',

minify: true,

},

})

```

tsconfig.json

```json

{

"compilerOptions": {

"target": "ES2022",

"module": "ES2022",

"lib": ["ES2022"],

"moduleResolution": "bundler",

"types": ["@cloudflare/workers-types/2023-07-01"],

"resolveJsonModule": true,

"allowJs": true,

"checkJs": false,

"strict": true,

"esModuleInterop": true,

"skipLibCheck": true,

"forceConsistentCasingInFileNames": true,

"isolatedModules": true,

"noEmit": true

},

"include": ["src/*/"],

"exclude": ["node_modules"]

}

```

---

API Route Patterns

Basic JSON Response

```typescript

app.get('/api/users', (c) => {

return c.json({

users: [

{ id: 1, name: 'Alice' },

{ id: 2, name: 'Bob' }

]

})

})

```

POST with Request Body

```typescript

app.post('/api/users', async (c) => {

const body = await c.req.json()

// Validate and process body

return c.json({ success: true, data: body }, 201)

})

```

Route Parameters

```typescript

app.get('/api/users/:id', (c) => {

const id = c.req.param('id')

return c.json({ id, name: 'User' })

})

```

Query Parameters

```typescript

app.get('/api/search', (c) => {

const query = c.req.query('q')

return c.json({ query, results: [] })

})

```

Error Handling

```typescript

app.get('/api/data', async (c) => {

try {

// Your logic here

return c.json({ success: true })

} catch (error) {

return c.json({ error: error.message }, 500)

}

})

```

Using Bindings (KV, D1, R2)

```typescript

type Bindings = {

ASSETS: Fetcher

MY_KV: KVNamespace

DB: D1Database

MY_BUCKET: R2Bucket

}

const app = new Hono<{ Bindings: Bindings }>()

app.get('/api/data', async (c) => {

// KV

const value = await c.env.MY_KV.get('key')

// D1

const result = await c.env.DB.prepare('SELECT * FROM users').all()

// R2

const object = await c.env.MY_BUCKET.get('file.txt')

return c.json({ value, result, object })

})

```

---

Static Assets Best Practices

Directory Structure

```

public/

β”œβ”€β”€ index.html # Main entry point

β”œβ”€β”€ styles.css # Global styles

β”œβ”€β”€ script.js # Client-side JavaScript

β”œβ”€β”€ favicon.ico # Favicon

└── assets/ # Images, fonts, etc.

β”œβ”€β”€ logo.png

└── fonts/

```

SPA Fallback

The "not_found_handling": "single-page-application" configuration means:

  • Unknown routes return index.html
  • Useful for React Router, Vue Router, etc.
  • BUT requires run_worker_first for API routes!

Route Priority

With "run_worker_first": ["/api/*"]:

  1. /api/hello β†’ Worker handles it (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)

Caching Static Assets

Static Assets are automatically cached at the edge. To bust cache:

```html

```

---

Development Workflow

Local Development

```bash

npm run dev

```

  • Server runs on http://localhost:8787
  • HMR enabled (file changes reload automatically)
  • Uses Miniflare for local simulation
  • All bindings work locally (KV, D1, R2)

Testing API Routes

```bash

# Test GET endpoint

curl http://localhost:8787/api/hello

# Test POST endpoint

curl -X POST http://localhost:8787/api/echo \

-H "Content-Type: application/json" \

-d '{"test": "data"}'

```

Type Generation

```bash

npm run cf-typegen

```

Generates worker-configuration.d.ts with:

  • Binding types (KV, D1, R2, etc.)
  • Environment variable types
  • Auto-completes in your editor

Deployment

```bash

# Deploy to production

npm run deploy

# Deploy to specific environment

wrangler deploy --env staging

# Tail logs in production

wrangler tail

# Check deployment status

wrangler deployments list

```

---

Complete Setup Checklist

  • [ ] Project scaffolded with npm create cloudflare@latest
  • [ ] Dependencies installed: hono@4.10.1, @cloudflare/vite-plugin@1.13.13
  • [ ] wrangler.jsonc created with:

- [ ] account_id set to your Cloudflare account

- [ ] assets.directory pointing to ./public/

- [ ] assets.run_worker_first includes /api/*

- [ ] compatibility_date set to recent date

  • [ ] vite.config.ts created with @cloudflare/vite-plugin
  • [ ] src/index.ts created with Hono app

- [ ] Uses export default app (NOT { fetch: app.fetch })

- [ ] Includes ASSETS binding type

- [ ] Has fallback route: app.all('*', (c) => c.env.ASSETS.fetch(c.req.raw))

  • [ ] public/ directory created with static files
  • [ ] npm run cf-typegen executed successfully
  • [ ] npm run dev starts without errors
  • [ ] API routes tested in browser/curl
  • [ ] Static assets serve correctly
  • [ ] HMR works without crashes
  • [ ] Ready to deploy with npm run deploy

---

Advanced Topics

Adding Middleware

```typescript

import { Hono } from 'hono'

import { logger } from 'hono/logger'

import { cors } from 'hono/cors'

const app = new Hono<{ Bindings: Bindings }>()

// Global middleware

app.use('*', logger())

app.use('/api/*', cors())

// Route-specific middleware

app.use('/admin/*', async (c, next) => {

// Auth check

await next()

})

```

Environment-Specific Configuration

```jsonc

// wrangler.jsonc

{

"name": "my-worker",

"env": {

"staging": {

"vars": { "ENV": "staging" }

},

"production": {

"vars": { "ENV": "production" }

}

}

}

```

Deploy: wrangler deploy --env staging

Custom Error Pages

```typescript

app.onError((err, c) => {

console.error(err)

return c.json({ error: 'Internal Server Error' }, 500)

})

app.notFound((c) => {

return c.json({ error: 'Not Found' }, 404)

})

```

Testing with Vitest

```bash

npm install -D vitest @cloudflare/vitest-pool-workers

```

Create vitest.config.ts:

```typescript

import { defineConfig } from 'vitest/config'

export default defineConfig({

test: {

poolOptions: {

workers: {

wrangler: { configPath: './wrangler.jsonc' },

},

},

},

})

```

See reference/testing.md for complete testing guide.

---

File Templates

All templates are available in the templates/ directory:

  • wrangler.jsonc - Complete Worker configuration
  • vite.config.ts - Vite + Cloudflare plugin setup
  • package.json - Dependencies and scripts
  • tsconfig.json - TypeScript configuration
  • src/index.ts - Hono app with API routes
  • public/index.html - Static frontend example
  • public/styles.css - Example styling
  • public/script.js - API test functions

Copy these files to your project and customize as needed.

---

Reference Documentation

For deeper understanding, see:

  • architecture.md - Deep dive into export patterns, routing, and Static Assets
  • common-issues.md - All 6 issues with detailed troubleshooting
  • deployment.md - Wrangler commands, CI/CD patterns, and production tips

---

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 Configuration: https://developers.cloudflare.com/workers/wrangler/configuration/
  • Hono: https://hono.dev/docs/getting-started/cloudflare-workers
  • Context7 Library ID: /websites/developers_cloudflare-workers

---

Dependencies (Latest Verified 2025-10-20)

```json

{

"dependencies": {

"hono": "^4.10.1"

},

"devDependencies": {

"@cloudflare/vite-plugin": "^1.13.13",

"@cloudflare/workers-types": "^4.20251011.0",

"vite": "^7.0.0",

"wrangler": "^4.43.0",

"typescript": "^5.9.0"

}

}

```

---

Production Example

This skill is based on the cloudflare-worker-base-test project:

  • Live: https://cloudflare-worker-base-test.webfonts.workers.dev
  • Build Time: ~45 minutes (actual)
  • Errors: 0 (all 6 known issues prevented)
  • Validation: βœ… Local dev, HMR, production deployment all successful

All patterns in this skill have been validated in production.

---

Questions? Issues?

  1. Check reference/common-issues.md first
  2. Verify all steps in the 4-step setup process
  3. Ensure export default app (not { fetch: app.fetch })
  4. Ensure run_worker_first is configured
  5. Check official docs: https://developers.cloudflare.com/workers/