smithery/ovachiever

cloudflare-worker-base

Set up Cloudflare Workers with Hono routing, Vite plugin, and Static Assets using production-tested patterns. Prevents 6 errors: export syntax, routing conflicts, HMR crashes, and Service Worker format confusion. Use when: creating Workers projects, configuring Hono or Vite for Workers, deploying with Wrangler, adding Static Assets with SPA fallback, or troubleshooting export syntax, API route conflicts, scheduled handlers, or HMR race conditions. wrangler.jsonc, ES Module, run_worker_first, SP…

Installation

$ npx skills add smithery/ovachiever --skill cloudflare-worker-base

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/ovachiever · top by installs.

npx skills add smithery/ovachiever

Browse all from smithery/ovachiever

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Skill metadata

Parsed from SKILL.md frontmatter.

LicenseMIT

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 18,003 B
  • docs SUMMARY.md 868 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

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@^7.0.0

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", // 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: runworkerfirst 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:

``typescript 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 "runworkerfirst": ["/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", // 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)

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 "notfoundhandling": "single-page-application" configuration means:

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

Route Priority

With "runworkerfirst": ["/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

- [ ] accountid set to your Cloudflare account - [ ] assets.directory pointing to ./public/ - [ ] assets.runworkerfirst includes /api/* - [ ] compatibilitydate 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 runworkerfirst is configured
  5. Check official docs: https://developers.cloudflare.com/workers/