mcpField
C
cloudflare-worker-base

cloudflare-worker-base

github.com/jackspaceCloud & DevOps1639 installsUpdated 12w ago
Repository

About cloudflare-worker-base

cloudflare-worker-base is a setup skill for Cloudflare Workers developers. It provides steps and templates for a Hono, Vite, and Wrangler project, along with guidance on avoiding documented configuration and deployment issues.

Key features

  1. 01Guides Workers project scaffolding
  2. 02Configures Hono, Vite, and Wrangler
  3. 03Includes configuration and app templates
  4. 04Covers routing, static assets, and APIs
  5. 05Documents development, type generation, and deployment
  6. 06Lists known issues and preventive steps

Use cases

  1. 01Create a Cloudflare Worker project from scratch
  2. 02Set up an app with API routes and static pages
  3. 03Configure local development and type generation
  4. 04Troubleshoot routing conflicts or deployment problems

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/jackspace/claudeskillz --skill cloudflare-worker-base

After installing, type/cloudflare-worker-basein Claude Code to confirm it loaded.

Bundled filesREADME.mdSKILL.json

Details

From the project README

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
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
cd my-worker
npm install [email protected]
npm install -D @cloudflare/[email protected] vite@latest

Version Notes:

  • [email protected]: Latest stable (verified 2025-10-20)
  • @cloudflare/[email protected]: Latest stable, fixes HMR race condition
  • vite: Latest version compatible with Cloudflare plugin
3. Configure Wrangler

Create or update wrangler.jsonc:

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-worker",
  "main": "src/index.ts",
  "account_id": "YOUR_ACCOUNT_ID",
  "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
4. Configure Vite

Create vite.config.ts:

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:

/**
 * 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
  • Using { fetch: app.fetch } causes: "Cannot read properties of undefined (reading 'map')"
  • Exception: If you need scheduled/tail handlers, use Module Worker format:
    export default {
      fetch: app.fetch,
      scheduled: async (event, env, ctx) => { /* ... */ }
    }
    
Step 2: Create Static Frontend

Create public/index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Worker App</title>
  <link rel="stylesheet" href="/styles.css">
</head>
<body>
  <div class="container">
    <h1>Cloudflare Worker + Static Assets</h1>
    <button onclick="testAPI()">Test API</button>
    <pre id="output"></pre>
  </div>
  <script src="/script.js"></script>
</body>
</html>

Create public/script.js:

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:

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:

{
  "scripts": {
    "dev": "wrangler dev",
    "deploy": "wrangler deploy",
    "cf-typegen": "wrangler types"
  }
}
Step 4: Test & Deploy
# 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 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 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 Prevention: Use Module Worker format when needed:

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 Prevention: Use @cloudflare/[email protected] or later

Issue #5: Static Assets Upload Race

Error: Non-deterministic deployment failures in CI/CD Source: workers-sdk #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)
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-worker",
  "main": "src/index.ts",
  "account_id": "YOUR_ACCOUNT_ID",
  "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)
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
{
  "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
app.get('/api/users', (c) => {
  return c.json({
    users: [
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' }
    ]
  })
})
POST with Request Body
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
app.get('/api/users/:id', (c) => {
  const id = c.req.param('id')
  return c.json({ id, name: 'User' })
})
Query Parameters
app.get('/api/search', (c) => {
  const query = c.req.query('q')
  return c.json({ query, results: [] })
})
Error Handling
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)
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:

<link rel="stylesheet" href="/styles.css?v=1.0.0">
<script src="/script.js?v=1.0.0"></script>

Development Workflow

Local Development
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
# 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
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
# 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: [email protected], @cloudflare/[email protected]
  • 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
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
// wrangler.jsonc
{
  "name": "my-worker",
  "env": {
    "staging": {
      "vars": { "ENV": "staging" }
    },
    "production": {
      "vars": { "ENV": "production" }
    }
  }
}

Deploy: wrangler deploy --env staging

Custom Error Pages
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
npm install -D vitest @cloudflare/vitest-pool-workers

Create vitest.config.ts:

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


Dependencies (Latest Verified 2025-10-20)

{
  "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:

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/

Comments

Markdown supported