# 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 1. [Issue #1: Export Syntax Error](#issue-1-export-syntax-error) 2. [Issue #2: Static Assets Routing Conflicts](#issue-2-static-assets-routing-conflicts) 3. [Issue #3: Scheduled/Cron Not Exported](#issue-3-scheduledcron-not-exported) 4. [Issue #4: HMR Race Condition](#issue-4-hmr-race-condition) 5. [Issue #5: Static Assets Upload Race](#issue-5-static-assets-upload-race) 6. [Issue #6: Service Worker Format Confusion](#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](https://github.com/honojs/hono/issues/3955) - **Related**: [honojs/vite-plugins #237](https://github.com/honojs/vite-plugins/issues/237) - **Reported**: February 2025 ### Root Cause When using Hono with Vite's build tools, the incorrect export pattern breaks the `this` context: ```typescript // ❌ WRONG: This causes the error export default { fetch: app.fetch } ``` **Why it breaks:** - Vite's bundler transforms the code - The `app.fetch` binding loses its `this` context - When Cloudflare calls `fetch()`, `this` is `undefined` - Hono tries to access `this.routes.map(...)` → Error ### Fix Use the direct export pattern: ```typescript 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: ```typescript 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 1. Check your `src/index.ts` export 2. Ensure it's `export default app` 3. Run `npm run dev` → Should start without errors 4. Run `npm run deploy` → Should deploy successfully 5. Test API endpoints → Should return JSON (not errors) --- ## Issue #2: Static Assets Routing Conflicts ### Symptoms - API routes return `index.html` instead 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 ```bash curl http://localhost:8787/api/hello # Expected: {"message":"Hello"} # Actual: ... ``` ### Source - **GitHub Issue**: [workers-sdk #8879](https://github.com/cloudflare/workers-sdk/issues/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`:** 1. Request to `/api/hello` 2. Static Assets handler checks: "Does `/api/hello` file exist?" 3. No → SPA fallback → Returns `public/index.html` 4. Your Worker never runs! ### Fix Add `run_worker_first` to `wrangler.jsonc`: ```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 ```jsonc { "assets": { "run_worker_first": [ "/api/*", "/auth/*", "/webhooks/*", "/_app/*" ] } } ``` ### How to Verify Fix 1. Start dev server: `npm run dev` 2. Test API endpoint: ```bash curl -i http://localhost:8787/api/hello ``` 3. Check response: - ✅ `Content-Type: application/json` - ✅ JSON body 4. Test static file: ```bash curl -i http://localhost:8787/ ``` 5. 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](https://github.com/honojs/vite-plugins/issues/275) - **Reported**: July 2025 ### Root Cause The `@hono/vite-build/cloudflare-workers` plugin **only supports the `fetch` handler**. If you use: ```typescript export default app // Only exports fetch handler ``` ...then scheduled/tail handlers are not exported. ### Fix Option 1: Use Module Worker Format ```typescript 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: ```bash npm uninstall @hono/vite-build npm install -D @cloudflare/vite-plugin ``` Update `vite.config.ts`: ```typescript 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 ```jsonc { "triggers": { "crons": ["0 0 * * *"] // Daily at midnight UTC } } ``` ### How to Verify Fix 1. Deploy: `npm run deploy` 2. Trigger manually: ```bash wrangler deploy && wrangler tail ``` 3. Wait for cron or trigger via dashboard 4. 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](https://github.com/cloudflare/workers-sdk/issues/9518) - **Related**: [workers-sdk #9249](https://github.com/cloudflare/workers-sdk/issues/9249) - **Reported**: June 2025 ### Root Cause **Race condition in `@cloudflare/vite-plugin` versions 1.1.1 through 1.11.x:** 1. File change detected 2. Vite triggers HMR 3. Plugin cancels old Worker instance 4. New instance starts before old one fully terminates 5. Promise cancellation error thrown ### Fix Update to latest `@cloudflare/vite-plugin`: ```bash 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: ```typescript // 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 1. Start dev server: `npm run dev` 2. Make rapid file changes (edit `src/index.ts` 5 times quickly) 3. Check terminal: - ✅ No "hanging Promise" errors - ✅ HMR updates smoothly 4. Test API endpoint after each change: ```bash 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](https://github.com/cloudflare/workers-sdk/issues/7555) - **Reported**: March 2025 ### Root Cause **Race condition during parallel asset uploads:** 1. Wrangler uploads multiple assets simultaneously 2. Cloudflare's asset store processes uploads 3. Manifest is generated before all uploads complete 4. 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: ```bash 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 ```yaml # GitHub Actions example - name: Deploy to Cloudflare run: | for i in {1..3}; do npm run deploy && break || sleep 10 done ``` ```bash # 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: ```typescript // vite.config.ts export default defineConfig({ build: { rollupOptions: { output: { manualChunks: { vendor: ['react', 'react-dom'], }, }, }, }, }) ``` ### How to Verify Fix 1. Deploy locally 5 times: ```bash for i in {1..5}; do npm run deploy; done ``` 2. All deployments should succeed 3. Run in CI/CD pipeline 4. 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: ```typescript // ❌ 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 ```typescript // ✅ CORRECT: ES Module format export default { fetch(request, env, ctx) { return new Response('Hello World') } } ``` **With Hono:** ```typescript import { Hono } from 'hono' const app = new Hono() app.get('/', (c) => c.text('Hello World')) export default app ``` ### Migration Steps 1. **Remove `addEventListener`**: ```diff - addEventListener('fetch', (event) => { - event.respondWith(handleRequest(event.request)) - }) ``` 2. **Change to ES Module export**: ```diff + export default { + fetch(request, env, ctx) { + return handleRequest(request, env) + } + } ``` 3. **Update function signatures** to accept `env`: ```diff - async function handleRequest(request) { + async function handleRequest(request, env) { // Now you can access env.MY_KV, env.DB, etc. ``` 4. **Update `wrangler.toml` → `wrangler.jsonc`**: ```bash # Convert TOML to JSONC (preferred since Wrangler v3.91.0) ``` ### How to Verify Fix 1. Check `src/index.ts`: - ✅ No `addEventListener` - ✅ Has `export default` 2. Check you can access bindings: ```typescript const value = await env.MY_KV.get('key') ``` 3. TypeScript types work: ```typescript type Bindings = { MY_KV: KVNamespace } ``` --- ## General Troubleshooting Tips ### Check Package Versions ```bash npm list hono @cloudflare/vite-plugin wrangler ``` **Expected (as of 2025-10-20):** - `hono@4.10.1` - `@cloudflare/vite-plugin@1.13.13` - `wrangler@4.43.0` ### Clear Wrangler Cache ```bash rm -rf node_modules/.wrangler rm -rf .wrangler npm run dev ``` ### Check Wrangler Config ```bash wrangler whoami # Verify authentication wrangler dev --local # Test without deploying ``` ### Enable Verbose Logging ```bash 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 ```bash # 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: 1. **Check official docs**: - Cloudflare Workers: https://developers.cloudflare.com/workers/ - Hono: https://hono.dev/ 2. **Search GitHub issues**: - workers-sdk: https://github.com/cloudflare/workers-sdk/issues - hono: https://github.com/honojs/hono/issues 3. **Ask in Discord**: - Cloudflare Developers: https://discord.gg/cloudflaredev - Hono: https://discord.gg/hono 4. **Check Stack Overflow**: - Tag: `cloudflare-workers` --- **All issues documented with GitHub sources** ✅ **All fixes production-tested** ✅