14 KiB
Common Issues and Troubleshooting
Last Updated: 2025-10-20
This document details all 6 documented issues that commonly affect Cloudflare Workers projects, with detailed explanations and fixes.
Table of Contents
- Issue #1: Export Syntax Error
- Issue #2: Static Assets Routing Conflicts
- Issue #3: Scheduled/Cron Not Exported
- Issue #4: HMR Race Condition
- Issue #5: Static Assets Upload Race
- Issue #6: Service Worker Format Confusion
Issue #1: Export Syntax Error
Symptoms
Error: Cannot read properties of undefined (reading 'map')
Deployment fails with TypeError during build or runtime.
Source
- GitHub Issue: honojs/hono #3955
- Related: honojs/vite-plugins #237
- Reported: February 2025
Root Cause
When using Hono with Vite's build tools, the incorrect export pattern breaks the this context:
// ❌ WRONG: This causes the error
export default {
fetch: app.fetch
}
Why it breaks:
- Vite's bundler transforms the code
- The
app.fetchbinding loses itsthiscontext - When Cloudflare calls
fetch(),thisisundefined - Hono tries to access
this.routes.map(...)→ Error
Fix
Use the direct export pattern:
import { Hono } from 'hono'
const app = new Hono()
// Define routes...
// ✅ CORRECT
export default app
Why this works:
- Hono's app object already implements the fetch handler
- No context binding is lost
- Vite can properly bundle the code
Exception: When You Need Multiple Handlers
If you need scheduled/tail handlers, use Module Worker format:
export default {
fetch: app.fetch,
scheduled: async (event, env, ctx) => {
console.log('Cron triggered:', event.cron)
}
}
This works because Cloudflare's runtime handles the binding correctly for Module Workers.
How to Verify Fix
- Check your
src/index.tsexport - Ensure it's
export default app - Run
npm run dev→ Should start without errors - Run
npm run deploy→ Should deploy successfully - Test API endpoints → Should return JSON (not errors)
Issue #2: Static Assets Routing Conflicts
Symptoms
- API routes return
index.htmlinstead of JSON - API endpoints return status 200 but wrong content-type (text/html instead of application/json)
- Browser console shows HTML when expecting JSON
Example
curl http://localhost:8787/api/hello
# Expected: {"message":"Hello"}
# Actual: <!DOCTYPE html><html>...
Source
- GitHub Issue: workers-sdk #8879
- Reported: April 2025
Root Cause
The not_found_handling: "single-page-application" configuration creates a fallback:
Request → File not found → Return index.html
Without run_worker_first:
- Request to
/api/hello - Static Assets handler checks: "Does
/api/hellofile exist?" - No → SPA fallback → Returns
public/index.html - Your Worker never runs!
Fix
Add run_worker_first to wrangler.jsonc:
{
"assets": {
"directory": "./public/",
"binding": "ASSETS",
"not_found_handling": "single-page-application",
"run_worker_first": ["/api/*"] // ← CRITICAL
}
}
What this does:
- Requests matching
/api/*go to your Worker FIRST - If Worker doesn't handle it, then try Static Assets
- Ensures API routes are never intercepted by SPA fallback
Advanced Configuration
{
"assets": {
"run_worker_first": [
"/api/*",
"/auth/*",
"/webhooks/*",
"/_app/*"
]
}
}
How to Verify Fix
- Start dev server:
npm run dev - Test API endpoint:
curl -i http://localhost:8787/api/hello - Check response:
- ✅
Content-Type: application/json - ✅ JSON body
- ✅
- Test static file:
curl -i http://localhost:8787/ - Check response:
- ✅
Content-Type: text/html - ✅ HTML body
- ✅
Issue #3: Scheduled/Cron Not Exported
Symptoms
Error: Handler does not export a scheduled() function
Deployment succeeds, but cron triggers fail.
Source
- GitHub Issue: honojs/vite-plugins #275
- Reported: July 2025
Root Cause
The @hono/vite-build/cloudflare-workers plugin only supports the fetch handler.
If you use:
export default app // Only exports fetch handler
...then scheduled/tail handlers are not exported.
Fix Option 1: Use Module Worker Format
import { Hono } from 'hono'
const app = new Hono()
// Define routes...
// ✅ Export multiple handlers
export default {
fetch: app.fetch,
scheduled: async (event, env, ctx) => {
console.log('Cron triggered:', event.cron)
// Your scheduled logic here
},
tail: async (events, env, ctx) => {
// Tail handler logic
console.log('Tail events:', events)
}
}
Fix Option 2: Use @cloudflare/vite-plugin
Instead of @hono/vite-build/cloudflare-workers, use the official Cloudflare plugin:
npm uninstall @hono/vite-build
npm install -D @cloudflare/vite-plugin
Update vite.config.ts:
import { defineConfig } from 'vite'
import { cloudflare } from '@cloudflare/vite-plugin'
export default defineConfig({
plugins: [cloudflare()],
})
This plugin supports all handler types.
Configure Cron in wrangler.jsonc
{
"triggers": {
"crons": ["0 0 * * *"] // Daily at midnight UTC
}
}
How to Verify Fix
- Deploy:
npm run deploy - Trigger manually:
wrangler deploy && wrangler tail - Wait for cron or trigger via dashboard
- Check logs for scheduled handler output
Issue #4: HMR Race Condition
Symptoms
Error: A hanging Promise was canceled
- Development server crashes during file changes
- Happens with rapid HMR updates
- Requires manual restart
Source
- GitHub Issue: workers-sdk #9518
- Related: workers-sdk #9249
- Reported: June 2025
Root Cause
Race condition in @cloudflare/vite-plugin versions 1.1.1 through 1.11.x:
- File change detected
- Vite triggers HMR
- Plugin cancels old Worker instance
- New instance starts before old one fully terminates
- Promise cancellation error thrown
Fix
Update to latest @cloudflare/vite-plugin:
npm install -D @cloudflare/vite-plugin@1.13.13
Fixed in version 1.13.13 (October 2025)
Alternative: Configure Vite with Persistence
If updating doesn't fix it, try:
// vite.config.ts
import { defineConfig } from 'vite'
import { cloudflare } from '@cloudflare/vite-plugin'
export default defineConfig({
plugins: [
cloudflare({
persist: true, // Persist state between HMR updates
}),
],
})
How to Verify Fix
- Start dev server:
npm run dev - Make rapid file changes (edit
src/index.ts5 times quickly) - Check terminal:
- ✅ No "hanging Promise" errors
- ✅ HMR updates smoothly
- Test API endpoint after each change:
curl http://localhost:8787/api/hello
Issue #5: Static Assets Upload Race
Symptoms
- Deployment fails non-deterministically in CI/CD
- Works locally, fails in CI randomly
- Error messages vary:
- "Failed to upload assets"
- "Timeout during asset upload"
- "Asset manifest mismatch"
Source
- GitHub Issue: workers-sdk #7555
- Reported: March 2025
Root Cause
Race condition during parallel asset uploads:
- Wrangler uploads multiple assets simultaneously
- Cloudflare's asset store processes uploads
- Manifest is generated before all uploads complete
- Deployment validation fails
Most common in CI/CD because:
- Network latency varies
- Parallel execution timing is different
- No user interaction to retry
Fix Option 1: Use Wrangler 4.x+ (Recommended)
Wrangler 4.x includes improved upload logic:
npm install -D wrangler@latest
Improvements in 4.x:
- Sequential upload of critical assets
- Better retry logic
- Manifest generation after all uploads complete
Fix Option 2: Add Retry Logic to CI/CD
# GitHub Actions example
- name: Deploy to Cloudflare
run: |
for i in {1..3}; do
npm run deploy && break || sleep 10
done
# Shell script
#!/bin/bash
for i in {1..3}; do
npm run deploy && break || sleep 10
done
Fix Option 3: Reduce Asset Count
If you have many small files, bundle them:
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
},
},
},
},
})
How to Verify Fix
- Deploy locally 5 times:
for i in {1..5}; do npm run deploy; done - All deployments should succeed
- Run in CI/CD pipeline
- Check logs for upload errors
Issue #6: Service Worker Format Confusion
Symptoms
- Using deprecated
addEventListener('fetch', ...)pattern - TypeScript errors about missing types
- Bindings don't work (KV, D1, R2)
- Modern Cloudflare features unavailable
Source
- Cloudflare Migration Guide: https://developers.cloudflare.com/workers/configuration/compatibility-dates/
- Multiple Stack Overflow questions (2024-2025)
Root Cause
Old tutorials and templates still use the deprecated Service Worker format:
// ❌ DEPRECATED: Service Worker format
addEventListener('fetch', (event) => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
return new Response('Hello World')
}
Problems with this format:
- Doesn't support bindings (KV, D1, R2, etc.)
- No TypeScript types
- No environment variable access
- Deprecated since Workers v2 (2021)
Fix: Use ES Module Format
// ✅ CORRECT: ES Module format
export default {
fetch(request, env, ctx) {
return new Response('Hello World')
}
}
With Hono:
import { Hono } from 'hono'
const app = new Hono()
app.get('/', (c) => c.text('Hello World'))
export default app
Migration Steps
-
Remove
addEventListener:- addEventListener('fetch', (event) => { - event.respondWith(handleRequest(event.request)) - }) -
Change to ES Module export:
+ export default { + fetch(request, env, ctx) { + return handleRequest(request, env) + } + } -
Update function signatures to accept
env:- async function handleRequest(request) { + async function handleRequest(request, env) { // Now you can access env.MY_KV, env.DB, etc. -
Update
wrangler.toml→wrangler.jsonc:# Convert TOML to JSONC (preferred since Wrangler v3.91.0)
How to Verify Fix
- Check
src/index.ts:- ✅ No
addEventListener - ✅ Has
export default
- ✅ No
- Check you can access bindings:
const value = await env.MY_KV.get('key') - TypeScript types work:
type Bindings = { MY_KV: KVNamespace }
General Troubleshooting Tips
Check Package Versions
npm list hono @cloudflare/vite-plugin wrangler
Expected (as of 2025-10-20):
hono@4.10.1@cloudflare/vite-plugin@1.13.13wrangler@4.43.0
Clear Wrangler Cache
rm -rf node_modules/.wrangler
rm -rf .wrangler
npm run dev
Check Wrangler Config
wrangler whoami # Verify authentication
wrangler dev --local # Test without deploying
Enable Verbose Logging
WRANGLER_LOG=debug npm run dev
WRANGLER_LOG=debug npm run deploy
Check Browser Console
Many issues are visible in the browser:
- Open DevTools → Network tab
- Check response Content-Type
- Check response body
- Look for CORS errors
Test with curl
# Test API endpoint
curl -i http://localhost:8787/api/hello
# Test POST
curl -X POST http://localhost:8787/api/echo \
-H "Content-Type: application/json" \
-d '{"test":"data"}'
# Test static file
curl -i http://localhost:8787/styles.css
Issue Summary Table
| Issue | Error Message | Source | Fix |
|---|---|---|---|
| #1 | "Cannot read properties of undefined" | hono #3955 | export default app |
| #2 | API routes return HTML | workers-sdk #8879 | run_worker_first: ["/api/*"] |
| #3 | "Handler does not export scheduled()" | vite-plugins #275 | Module Worker format or @cloudflare/vite-plugin |
| #4 | "A hanging Promise was canceled" | workers-sdk #9518 | Update to vite-plugin@1.13.13+ |
| #5 | Non-deterministic deployment failures | workers-sdk #7555 | Use Wrangler 4.x+ with retry |
| #6 | Service Worker format issues | Cloudflare migration | Use ES Module format |
Getting Help
If you encounter issues not covered here:
-
Check official docs:
- Cloudflare Workers: https://developers.cloudflare.com/workers/
- Hono: https://hono.dev/
-
Search GitHub issues:
-
Ask in Discord:
- Cloudflare Developers: https://discord.gg/cloudflaredev
- Hono: https://discord.gg/hono
-
Check Stack Overflow:
- Tag:
cloudflare-workers
- Tag:
All issues documented with GitHub sources ✅ All fixes production-tested ✅