Initial commit
This commit is contained in:
12
.claude-plugin/plugin.json
Normal file
12
.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "cloudflare-worker-base",
|
||||||
|
"description": "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.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "Jeremy Dawes",
|
||||||
|
"email": "jeremy@jezweb.net"
|
||||||
|
},
|
||||||
|
"skills": [
|
||||||
|
"./"
|
||||||
|
]
|
||||||
|
}
|
||||||
3
README.md
Normal file
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 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.
|
||||||
195
SKILL.md
Normal file
195
SKILL.md
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
---
|
||||||
|
name: cloudflare-worker-base
|
||||||
|
description: |
|
||||||
|
Set up Cloudflare Workers with Hono routing, Vite plugin, and Static Assets using production-tested patterns.
|
||||||
|
Prevents 8 errors: export syntax, routing conflicts, HMR crashes, gradual rollout asset mismatches, and free tier 429s.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Keywords: Cloudflare Workers, CF Workers, Hono, wrangler, Vite, Static Assets, @cloudflare/vite-plugin,
|
||||||
|
wrangler.jsonc, ES Module, run_worker_first, SPA fallback, API routes, serverless, edge computing,
|
||||||
|
"Cannot read properties of undefined", "Static Assets 404", "A hanging Promise was canceled",
|
||||||
|
"Handler does not export", deployment fails, routing not working, HMR crashes
|
||||||
|
license: MIT
|
||||||
|
---
|
||||||
|
|
||||||
|
# Cloudflare Worker Base Stack
|
||||||
|
|
||||||
|
**Production-tested**: cloudflare-worker-base-test (https://cloudflare-worker-base-test.webfonts.workers.dev)
|
||||||
|
**Last Updated**: 2025-11-24
|
||||||
|
**Status**: Production Ready ✅
|
||||||
|
**Latest Versions**: hono@4.10.6, @cloudflare/vite-plugin@1.15.2, vite@7.2.4, wrangler@4.50.0
|
||||||
|
|
||||||
|
**Recent Updates (2025)**:
|
||||||
|
- **March 2025**: Wrangler v4 release (minimal breaking changes, v3 supported until Q1 2027)
|
||||||
|
- **June 2025**: Native Integrations removed from dashboard (CLI-based approach with wrangler secrets)
|
||||||
|
- **2025 Platform**: Workers VPC Services, Durable Objects Data Studio, 64 env vars (5KB each), unlimited Cron Triggers per Worker, WebSocket 32 MiB messages, node:fs/Web File System APIs
|
||||||
|
- **2025 Static Assets**: Gradual rollout asset mismatch issue, free tier 429 errors with run_worker_first, Vite plugin auto-detection
|
||||||
|
- **Hono 4.10.x**: Enhanced TypeScript RPC type inference, cloneRawRequest utility, JWT aud validation, auth middleware improvements
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Start (5 Minutes)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Scaffold project
|
||||||
|
npm create cloudflare@latest my-worker -- --type hello-world --ts --git --deploy false --framework none
|
||||||
|
|
||||||
|
# 2. Install dependencies
|
||||||
|
cd my-worker
|
||||||
|
npm install hono@4.10.6
|
||||||
|
npm install -D @cloudflare/vite-plugin@1.15.2 vite@7.2.4
|
||||||
|
|
||||||
|
# 3. Create wrangler.jsonc
|
||||||
|
{
|
||||||
|
"name": "my-worker",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"account_id": "YOUR_ACCOUNT_ID",
|
||||||
|
"compatibility_date": "2025-11-11",
|
||||||
|
"assets": {
|
||||||
|
"directory": "./public/",
|
||||||
|
"binding": "ASSETS",
|
||||||
|
"not_found_handling": "single-page-application",
|
||||||
|
"run_worker_first": ["/api/*"] // CRITICAL: Prevents SPA fallback from intercepting API routes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 4. Create vite.config.ts
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import { cloudflare } from '@cloudflare/vite-plugin'
|
||||||
|
export default defineConfig({ plugins: [cloudflare()] })
|
||||||
|
|
||||||
|
# 5. Create src/index.ts
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
type Bindings = { ASSETS: Fetcher }
|
||||||
|
const app = new Hono<{ Bindings: Bindings }>()
|
||||||
|
app.get('/api/hello', (c) => c.json({ message: 'Hello!' }))
|
||||||
|
app.all('*', (c) => c.env.ASSETS.fetch(c.req.raw))
|
||||||
|
export default app // CRITICAL: Use this pattern (NOT { fetch: app.fetch })
|
||||||
|
|
||||||
|
# 6. Deploy
|
||||||
|
npm run dev # Local: http://localhost:8787
|
||||||
|
wrangler deploy # Production
|
||||||
|
```
|
||||||
|
|
||||||
|
**Critical Configuration**:
|
||||||
|
- `run_worker_first: ["/api/*"]` - Without this, SPA fallback intercepts API routes returning `index.html` instead of JSON ([workers-sdk #8879](https://github.com/cloudflare/workers-sdk/issues/8879))
|
||||||
|
- `export default app` - Using `{ fetch: app.fetch }` causes "Cannot read properties of undefined" ([honojs/hono #3955](https://github.com/honojs/hono/issues/3955))
|
||||||
|
|
||||||
|
|
||||||
|
## Known Issues Prevention
|
||||||
|
|
||||||
|
This skill prevents **8 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
|
||||||
|
|
||||||
|
### Issue #7: Gradual Rollouts Asset Mismatch (2025)
|
||||||
|
**Error**: 404 errors for fingerprinted assets during gradual deployments
|
||||||
|
**Source**: [Cloudflare Static Assets Docs](https://developers.cloudflare.com/workers/static-assets/routing/advanced/gradual-rollouts)
|
||||||
|
**Why It Happens**: Modern frameworks (React/Vue/Angular with Vite) generate fingerprinted filenames (e.g., `index-a1b2c3d4.js`). During gradual rollouts between versions, a user's initial request may go to Version A (HTML references `index-a1b2c3d4.js`), but subsequent asset requests route to Version B (only has `index-m3n4o5p6.js`), causing 404s
|
||||||
|
**Prevention**:
|
||||||
|
- Avoid gradual deployments with fingerprinted assets
|
||||||
|
- Use instant cutover deployments for static sites
|
||||||
|
- Or implement version-aware routing with custom logic
|
||||||
|
|
||||||
|
### Issue #8: Free Tier 429 Errors with run_worker_first (2025)
|
||||||
|
**Error**: 429 (Too Many Requests) responses on asset requests when exceeding free tier limits
|
||||||
|
**Source**: [Cloudflare Static Assets Billing Docs](https://developers.cloudflare.com/workers/static-assets/billing-and-limitations)
|
||||||
|
**Why It Happens**: When using `run_worker_first`, requests matching specified patterns ALWAYS invoke your Worker script (counted toward free tier limits). After exceeding limits, these requests receive 429 instead of falling back to free static asset serving
|
||||||
|
**Prevention**:
|
||||||
|
- Upgrade to Workers Paid plan ($5/month) for unlimited requests
|
||||||
|
- Use negative patterns (`!/pattern`) to exclude paths from Worker invocation
|
||||||
|
- Minimize `run_worker_first` patterns to only essential API routes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Route Priority with run_worker_first
|
||||||
|
|
||||||
|
**Critical Understanding**: `"not_found_handling": "single-page-application"` returns `index.html` for unknown routes (enables React Router, Vue Router). Without `run_worker_first`, this intercepts API routes!
|
||||||
|
|
||||||
|
**Request Routing with `run_worker_first: ["/api/*"]`**:
|
||||||
|
1. `/api/hello` → Worker handles (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)
|
||||||
|
|
||||||
|
**Static Assets Caching**: Automatic edge caching. Cache bust with query strings: `<link href="/styles.css?v=1.0.0">`
|
||||||
|
|
||||||
|
**Free Tier Warning** (2025): `run_worker_first` patterns count toward free tier limits. After exceeding, requests get 429 instead of falling back to free static assets. Use negative patterns (`!/pattern`) or upgrade to Paid plan.
|
||||||
|
|
||||||
|
|
||||||
|
## Bundled Resources
|
||||||
|
|
||||||
|
**Templates**: Complete setup files in `templates/` directory (wrangler.jsonc, vite.config.ts, package.json, tsconfig.json, src/index.ts, public/index.html, styles.css, script.js)
|
||||||
|
|
||||||
|
|
||||||
|
## 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**: https://developers.cloudflare.com/workers/wrangler/
|
||||||
|
- **Hono**: https://hono.dev/docs/getting-started/cloudflare-workers
|
||||||
|
- **MCP Tool**: Use `mcp__cloudflare-docs__search_cloudflare_documentation` for latest docs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies (Latest Verified 2025-11-24)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"hono": "^4.10.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@cloudflare/vite-plugin": "^1.15.2",
|
||||||
|
"@cloudflare/workers-types": "^4.20251121.0",
|
||||||
|
"vite": "^7.2.4",
|
||||||
|
"wrangler": "^4.50.0",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Production Validation
|
||||||
|
|
||||||
|
**Live Example**: https://cloudflare-worker-base-test.webfonts.workers.dev (build time: 45 min, 0 errors, all 8 issues prevented)
|
||||||
|
|
||||||
89
plugin.lock.json
Normal file
89
plugin.lock.json
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"$schema": "internal://schemas/plugin.lock.v1.json",
|
||||||
|
"pluginId": "gh:jezweb/claude-skills:skills/cloudflare-worker-base",
|
||||||
|
"normalized": {
|
||||||
|
"repo": null,
|
||||||
|
"ref": "refs/tags/v20251128.0",
|
||||||
|
"commit": "b61bb695bc7ea03aa2d6a1257968e8c79c19f582",
|
||||||
|
"treeHash": "d10a39858a7c02c469ba337d1de58a851f9b46fafbdfbe9b5493c53f9894fe32",
|
||||||
|
"generatedAt": "2025-11-28T10:18:55.745950Z",
|
||||||
|
"toolVersion": "publish_plugins.py@0.2.0"
|
||||||
|
},
|
||||||
|
"origin": {
|
||||||
|
"remote": "git@github.com:zhongweili/42plugin-data.git",
|
||||||
|
"branch": "master",
|
||||||
|
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
|
||||||
|
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
|
||||||
|
},
|
||||||
|
"manifest": {
|
||||||
|
"name": "cloudflare-worker-base",
|
||||||
|
"description": "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.",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "README.md",
|
||||||
|
"sha256": "4056f75b72af927d3b0f5bb4e1a8871e71531756210491f1d8a6c83e951020cb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "SKILL.md",
|
||||||
|
"sha256": "1a326141fce783e31db73111cb99173f4281cac2be8c65c9c0536148b676f82a"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/architecture.md",
|
||||||
|
"sha256": "d3fd27f33955a41dd4ef73fea6dac6d36b37807c4a291d2e06c8f2f43d9d4e3d"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/common-issues.md",
|
||||||
|
"sha256": "e9cec8e56157555b9fb0505468d521cf06245745db80f325ab796ad0ace92e3e"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "references/deployment.md",
|
||||||
|
"sha256": "d9b4f83effbc5ea3f1f7f2a56e4ac53057245a3c8b11ce6b7bcbfc2a73c5ce19"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": ".claude-plugin/plugin.json",
|
||||||
|
"sha256": "2c75f5bcf8d188a482bf67dac1e7def7e34b3825e47c762beae7d21cbfd6f1d7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "templates/wrangler.jsonc",
|
||||||
|
"sha256": "dba230edb1754e4bfe4f1adf5039f1a9f52c4c912c09d3101856b7f8b4c89bd7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "templates/package.json",
|
||||||
|
"sha256": "5778d01947d2f62e046415c65ccfa8f29388bba9fb5f92e9241336a86de7e308"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "templates/tsconfig.json",
|
||||||
|
"sha256": "6956104e94fa7add588ceb73718ee8dac3e44c1427b6e9c647e1a9961b1873e6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "templates/vite.config.ts",
|
||||||
|
"sha256": "5d2062acc1fa802f8ca1fe41ca07ef45d4ec97fe6efef83dc8e03ff8fad37fe2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "templates/public/index.html",
|
||||||
|
"sha256": "666f828cfb91306baf0b318cbcc33cfdb703013a0c4ea54af0ff50ad641f9fa7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "templates/public/styles.css",
|
||||||
|
"sha256": "8bff3ff271f32820ac2e3d0493caa3aa59c99165ce11af52cf3fa59e49d772d3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "templates/public/script.js",
|
||||||
|
"sha256": "cc4fa2c001fb8b12fbb95e91d9bdbd498d0aab56305fa7b3b0d6023bd19268f8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "templates/src/index.ts",
|
||||||
|
"sha256": "c0a8b998f0e1d90261d4ca9d7e81513ce5fd92922aca61cf71020eac56162b10"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dirSha256": "d10a39858a7c02c469ba337d1de58a851f9b46fafbdfbe9b5493c53f9894fe32"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"scannedAt": null,
|
||||||
|
"scannerVersion": null,
|
||||||
|
"flags": []
|
||||||
|
}
|
||||||
|
}
|
||||||
529
references/architecture.md
Normal file
529
references/architecture.md
Normal file
@@ -0,0 +1,529 @@
|
|||||||
|
# Architecture Deep Dive
|
||||||
|
|
||||||
|
**Last Updated**: 2025-10-20
|
||||||
|
|
||||||
|
This document explains the architectural patterns used in Cloudflare Workers with Hono, Vite, and Static Assets.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Export Patterns](#export-patterns)
|
||||||
|
2. [Routing Architecture](#routing-architecture)
|
||||||
|
3. [Static Assets Integration](#static-assets-integration)
|
||||||
|
4. [Bindings and Type Safety](#bindings-and-type-safety)
|
||||||
|
5. [Development vs Production](#development-vs-production)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Export Patterns
|
||||||
|
|
||||||
|
### The Correct Pattern (ES Module Format)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
|
||||||
|
// Define routes...
|
||||||
|
|
||||||
|
// ✅ CORRECT: Export the Hono app directly
|
||||||
|
export default app
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this works:**
|
||||||
|
- Hono's app object already implements the `fetch` handler
|
||||||
|
- When Cloudflare calls your Worker, it automatically invokes `app.fetch()`
|
||||||
|
- This is the **ES Module Worker format** (modern, recommended)
|
||||||
|
|
||||||
|
### The Incorrect Pattern (Causes Errors)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ WRONG: This causes "Cannot read properties of undefined (reading 'map')" error
|
||||||
|
export default {
|
||||||
|
fetch: app.fetch
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why this fails:**
|
||||||
|
- When using Vite's build tools with Hono, the `app.fetch` binding is lost
|
||||||
|
- The Vite bundler transforms the code in a way that breaks the `this` context
|
||||||
|
- Source: [honojs/hono #3955](https://github.com/honojs/hono/issues/3955)
|
||||||
|
|
||||||
|
### Module Worker Format (When You Need Multiple Handlers)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
|
||||||
|
// Define routes...
|
||||||
|
|
||||||
|
// ✅ CORRECT: Use Module Worker format for scheduled/tail handlers
|
||||||
|
export default {
|
||||||
|
fetch: app.fetch,
|
||||||
|
|
||||||
|
scheduled: async (event, env, ctx) => {
|
||||||
|
// Cron job logic
|
||||||
|
console.log('Cron triggered:', event.cron)
|
||||||
|
},
|
||||||
|
|
||||||
|
tail: async (events, env, ctx) => {
|
||||||
|
// Tail handler logic
|
||||||
|
console.log('Tail events:', events)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**When to use this:**
|
||||||
|
- You need scheduled (cron) handlers
|
||||||
|
- You need tail handlers for log consumption
|
||||||
|
- You need queue consumers
|
||||||
|
- You need durable object handlers
|
||||||
|
|
||||||
|
**Important**: This is still ES Module format, not the deprecated Service Worker format.
|
||||||
|
|
||||||
|
### Deprecated Service Worker Format (Never Use)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ DEPRECATED: Never use this format
|
||||||
|
addEventListener('fetch', (event) => {
|
||||||
|
event.respondWith(handleRequest(event.request))
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why never use this:**
|
||||||
|
- Deprecated since Cloudflare Workers v2
|
||||||
|
- Doesn't support modern features (D1, Vectorize, etc.)
|
||||||
|
- Not compatible with TypeScript types
|
||||||
|
- Not supported by Vite plugin
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Routing Architecture
|
||||||
|
|
||||||
|
### Request Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Incoming Request
|
||||||
|
↓
|
||||||
|
├─→ Worker checks run_worker_first patterns
|
||||||
|
│ └─→ Matches /api/* → Worker handles it → Returns JSON
|
||||||
|
│
|
||||||
|
└─→ No match → Static Assets handler
|
||||||
|
├─→ File exists → Returns file
|
||||||
|
└─→ File not found → SPA fallback → Returns index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration Required
|
||||||
|
|
||||||
|
In `wrangler.jsonc`:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"assets": {
|
||||||
|
"directory": "./public/",
|
||||||
|
"binding": "ASSETS",
|
||||||
|
"not_found_handling": "single-page-application",
|
||||||
|
"run_worker_first": ["/api/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Critical**: Without `run_worker_first`, the SPA fallback intercepts ALL requests, including API routes.
|
||||||
|
|
||||||
|
### Route Priority
|
||||||
|
|
||||||
|
1. **Worker routes** (if matched by `run_worker_first`)
|
||||||
|
```typescript
|
||||||
|
app.get('/api/hello', (c) => c.json({ ... }))
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Static files** (if file exists in `public/`)
|
||||||
|
```
|
||||||
|
public/styles.css → Served as-is
|
||||||
|
public/logo.png → Served as-is
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **SPA fallback** (if file doesn't exist)
|
||||||
|
```
|
||||||
|
/unknown-route → Returns public/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
### Advanced Routing Patterns
|
||||||
|
|
||||||
|
#### Wildcard Routes
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Match all API versions
|
||||||
|
app.get('/api/:version/users', (c) => {
|
||||||
|
const version = c.req.param('version')
|
||||||
|
return c.json({ version })
|
||||||
|
})
|
||||||
|
|
||||||
|
// Match nested routes
|
||||||
|
app.get('/api/users/:id/posts/:postId', (c) => {
|
||||||
|
const { id, postId } = c.req.param()
|
||||||
|
return c.json({ userId: id, postId })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Regex Routes
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Match numeric IDs only
|
||||||
|
app.get('/api/users/:id{[0-9]+}', (c) => {
|
||||||
|
const id = c.req.param('id')
|
||||||
|
return c.json({ id: parseInt(id) })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Route Groups
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const api = new Hono()
|
||||||
|
|
||||||
|
api.get('/users', (c) => c.json({ users: [] }))
|
||||||
|
api.get('/posts', (c) => c.json({ posts: [] }))
|
||||||
|
|
||||||
|
app.route('/api', api) // Mount at /api
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Static Assets Integration
|
||||||
|
|
||||||
|
### How Static Assets Work
|
||||||
|
|
||||||
|
1. **Upload**: When you deploy, Wrangler uploads `public/` to Cloudflare's asset store
|
||||||
|
2. **Binding**: Your Worker receives an `ASSETS` Fetcher binding
|
||||||
|
3. **Request**: Your Worker can forward requests to `ASSETS.fetch()`
|
||||||
|
4. **Cache**: Assets are cached at the edge automatically
|
||||||
|
|
||||||
|
### The Fallback Pattern
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Handle all unmatched routes
|
||||||
|
app.all('*', (c) => {
|
||||||
|
// Forward to Static Assets
|
||||||
|
return c.env.ASSETS.fetch(c.req.raw)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**What this does:**
|
||||||
|
- Forwards request to Static Assets handler
|
||||||
|
- Static Assets checks if file exists
|
||||||
|
- If yes: Returns file
|
||||||
|
- If no: Returns `index.html` (SPA fallback)
|
||||||
|
|
||||||
|
### Custom 404 Handling
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.all('*', async (c) => {
|
||||||
|
const response = await c.env.ASSETS.fetch(c.req.raw)
|
||||||
|
|
||||||
|
// If Static Assets returns 404, customize response
|
||||||
|
if (response.status === 404) {
|
||||||
|
return c.json({ error: 'Not Found' }, 404)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Asset Preprocessing
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.all('*', async (c) => {
|
||||||
|
const url = new URL(c.req.url)
|
||||||
|
|
||||||
|
// Rewrite /old-path to /new-path
|
||||||
|
if (url.pathname === '/old-path') {
|
||||||
|
url.pathname = '/new-path'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new request with modified URL
|
||||||
|
const modifiedRequest = new Request(url, c.req.raw)
|
||||||
|
return c.env.ASSETS.fetch(modifiedRequest)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bindings and Type Safety
|
||||||
|
|
||||||
|
### Defining Bindings
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type Bindings = {
|
||||||
|
ASSETS: Fetcher // Static Assets (always present)
|
||||||
|
MY_KV: KVNamespace // KV namespace
|
||||||
|
DB: D1Database // D1 database
|
||||||
|
MY_BUCKET: R2Bucket // R2 bucket
|
||||||
|
MY_VAR: string // Environment variable
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = new Hono<{ Bindings: Bindings }>()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accessing Bindings
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.get('/api/data', async (c) => {
|
||||||
|
// Type-safe access to bindings
|
||||||
|
const value = await c.env.MY_KV.get('key')
|
||||||
|
const result = await c.env.DB.prepare('SELECT * FROM users').all()
|
||||||
|
const object = await c.env.MY_BUCKET.get('file.txt')
|
||||||
|
const variable = c.env.MY_VAR
|
||||||
|
|
||||||
|
return c.json({ value, result, object, variable })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Auto-Generated Types
|
||||||
|
|
||||||
|
Run `wrangler types` to generate `worker-configuration.d.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Auto-generated by Wrangler
|
||||||
|
interface Env {
|
||||||
|
ASSETS: Fetcher
|
||||||
|
MY_KV: KVNamespace
|
||||||
|
DB: D1Database
|
||||||
|
MY_BUCKET: R2Bucket
|
||||||
|
MY_VAR: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const app = new Hono<{ Bindings: Env }>()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development vs Production
|
||||||
|
|
||||||
|
### Local Development (wrangler dev)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
**What happens:**
|
||||||
|
- Miniflare simulates Cloudflare's runtime locally
|
||||||
|
- Bindings are emulated (KV, D1, R2)
|
||||||
|
- HMR enabled via Vite plugin
|
||||||
|
- Runs on http://localhost:8787
|
||||||
|
|
||||||
|
**Configuration**:
|
||||||
|
```typescript
|
||||||
|
// vite.config.ts
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
cloudflare({
|
||||||
|
persist: true, // Persist data between restarts
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production Deployment (wrangler deploy)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
**What happens:**
|
||||||
|
- Vite builds your code
|
||||||
|
- Wrangler uploads to Cloudflare
|
||||||
|
- Static Assets uploaded separately
|
||||||
|
- Worker deployed to edge network
|
||||||
|
|
||||||
|
**Build Output**:
|
||||||
|
```
|
||||||
|
dist/
|
||||||
|
├── index.js # Your Worker code (bundled)
|
||||||
|
└── ... # Other build artifacts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment-Specific Configuration
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
// wrangler.jsonc
|
||||||
|
{
|
||||||
|
"name": "my-worker",
|
||||||
|
"env": {
|
||||||
|
"staging": {
|
||||||
|
"name": "my-worker-staging",
|
||||||
|
"vars": { "ENV": "staging" },
|
||||||
|
"kv_namespaces": [
|
||||||
|
{ "binding": "MY_KV", "id": "staging-kv-id" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"production": {
|
||||||
|
"name": "my-worker-production",
|
||||||
|
"vars": { "ENV": "production" },
|
||||||
|
"kv_namespaces": [
|
||||||
|
{ "binding": "MY_KV", "id": "production-kv-id" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy to specific environment:
|
||||||
|
```bash
|
||||||
|
wrangler deploy --env staging
|
||||||
|
wrangler deploy --env production
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Detection in Code
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.get('/api/info', (c) => {
|
||||||
|
const isDev = c.req.url.includes('localhost')
|
||||||
|
const env = c.env.ENV || 'development'
|
||||||
|
|
||||||
|
return c.json({ isDev, env })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### Cold Starts
|
||||||
|
|
||||||
|
Cloudflare Workers have **extremely fast cold starts** (~5ms):
|
||||||
|
- Code is distributed globally
|
||||||
|
- No containers to spin up
|
||||||
|
- Minimal initialization overhead
|
||||||
|
|
||||||
|
Keep your bundle small:
|
||||||
|
- Avoid large dependencies
|
||||||
|
- Use tree-shaking (Vite does this automatically)
|
||||||
|
- Lazy-load heavy modules
|
||||||
|
|
||||||
|
### CPU Time Limits
|
||||||
|
|
||||||
|
- **Free Plan**: 10ms CPU time per request
|
||||||
|
- **Paid Plan**: 50ms CPU time per request
|
||||||
|
|
||||||
|
**Tip**: Use asynchronous operations (KV, D1, R2) to avoid blocking CPU time.
|
||||||
|
|
||||||
|
### Memory Limits
|
||||||
|
|
||||||
|
- **128 MB** per Worker instance
|
||||||
|
|
||||||
|
**Tip**: Avoid loading large files into memory. Stream data when possible.
|
||||||
|
|
||||||
|
### Request Size Limits
|
||||||
|
|
||||||
|
- **Request Body**: 100 MB
|
||||||
|
- **Response Body**: No limit (can stream)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### 1. Use Middleware for Common Logic
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { logger } from 'hono/logger'
|
||||||
|
import { cors } from 'hono/cors'
|
||||||
|
|
||||||
|
app.use('*', logger())
|
||||||
|
app.use('/api/*', cors())
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Separate API and Static Routes
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const api = new Hono()
|
||||||
|
|
||||||
|
api.get('/users', ...)
|
||||||
|
api.get('/posts', ...)
|
||||||
|
|
||||||
|
app.route('/api', api)
|
||||||
|
app.all('*', (c) => c.env.ASSETS.fetch(c.req.raw))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Handle Errors Gracefully
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.onError((err, c) => {
|
||||||
|
console.error(err)
|
||||||
|
return c.json({ error: 'Internal Server Error' }, 500)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Use TypeScript
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Define types for request/response
|
||||||
|
type User = {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get('/api/users/:id', async (c) => {
|
||||||
|
const id = parseInt(c.req.param('id'))
|
||||||
|
const user: User = { id, name: 'Alice' }
|
||||||
|
return c.json(user)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Validate Input
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { z } from 'zod'
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
name: z.string(),
|
||||||
|
email: z.string().email(),
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post('/api/users', async (c) => {
|
||||||
|
const body = await c.req.json()
|
||||||
|
const validated = schema.parse(body)
|
||||||
|
return c.json({ success: true, data: validated })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Issue: API routes return HTML
|
||||||
|
|
||||||
|
**Cause**: Missing `run_worker_first` configuration
|
||||||
|
|
||||||
|
**Fix**: Add to `wrangler.jsonc`:
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"assets": {
|
||||||
|
"run_worker_first": ["/api/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: HMR crashes with "A hanging Promise was canceled"
|
||||||
|
|
||||||
|
**Cause**: Race condition in older Vite plugin versions
|
||||||
|
|
||||||
|
**Fix**: Update to latest:
|
||||||
|
```bash
|
||||||
|
npm install -D @cloudflare/vite-plugin@1.13.13
|
||||||
|
```
|
||||||
|
|
||||||
|
### Issue: Deployment fails with "Cannot read properties of undefined"
|
||||||
|
|
||||||
|
**Cause**: Incorrect export pattern
|
||||||
|
|
||||||
|
**Fix**: Use `export default app` (not `{ fetch: app.fetch }`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**For more troubleshooting**, see `common-issues.md`.
|
||||||
645
references/common-issues.md
Normal file
645
references/common-issues.md
Normal file
@@ -0,0 +1,645 @@
|
|||||||
|
# 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: <!DOCTYPE html><html>...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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** ✅
|
||||||
856
references/deployment.md
Normal file
856
references/deployment.md
Normal file
@@ -0,0 +1,856 @@
|
|||||||
|
# Deployment Guide
|
||||||
|
|
||||||
|
**Last Updated**: 2025-10-20
|
||||||
|
|
||||||
|
Complete guide to deploying Cloudflare Workers with Wrangler, including CI/CD patterns and production best practices.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Prerequisites](#prerequisites)
|
||||||
|
2. [Wrangler Commands](#wrangler-commands)
|
||||||
|
3. [Environment Configuration](#environment-configuration)
|
||||||
|
4. [CI/CD Pipelines](#cicd-pipelines)
|
||||||
|
5. [Production Best Practices](#production-best-practices)
|
||||||
|
6. [Monitoring and Logs](#monitoring-and-logs)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### 1. Cloudflare Account
|
||||||
|
|
||||||
|
Sign up at https://dash.cloudflare.com/sign-up
|
||||||
|
|
||||||
|
### 2. Get Account ID
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Option 1: From dashboard
|
||||||
|
# Go to: Workers & Pages → Overview → Account ID (right sidebar)
|
||||||
|
|
||||||
|
# Option 2: Via Wrangler
|
||||||
|
wrangler whoami
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to `wrangler.jsonc`:
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"account_id": "YOUR_ACCOUNT_ID_HERE"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Authenticate Wrangler
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Login via browser
|
||||||
|
wrangler login
|
||||||
|
|
||||||
|
# Or use API token (for CI/CD)
|
||||||
|
export CLOUDFLARE_API_TOKEN="your-token"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Create API token**:
|
||||||
|
1. Go to: https://dash.cloudflare.com/profile/api-tokens
|
||||||
|
2. Click "Create Token"
|
||||||
|
3. Use template: "Edit Cloudflare Workers"
|
||||||
|
4. Copy token (only shown once!)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wrangler Commands
|
||||||
|
|
||||||
|
### Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start local dev server (http://localhost:8787)
|
||||||
|
wrangler dev
|
||||||
|
|
||||||
|
# Local mode (no network requests to Cloudflare)
|
||||||
|
wrangler dev --local
|
||||||
|
|
||||||
|
# Custom port
|
||||||
|
wrangler dev --port 3000
|
||||||
|
|
||||||
|
# Specific environment
|
||||||
|
wrangler dev --env staging
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Deploy to production
|
||||||
|
wrangler deploy
|
||||||
|
|
||||||
|
# Deploy to specific environment
|
||||||
|
wrangler deploy --env staging
|
||||||
|
wrangler deploy --env production
|
||||||
|
|
||||||
|
# Dry run (validate without deploying)
|
||||||
|
wrangler deploy --dry-run
|
||||||
|
|
||||||
|
# Deploy with compatibility date
|
||||||
|
wrangler deploy --compatibility-date 2025-10-11
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type Generation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate TypeScript types for bindings
|
||||||
|
wrangler types
|
||||||
|
|
||||||
|
# Output to custom file
|
||||||
|
wrangler types --output-file=types/worker.d.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Tail live logs
|
||||||
|
wrangler tail
|
||||||
|
|
||||||
|
# Filter by status code
|
||||||
|
wrangler tail --status error
|
||||||
|
|
||||||
|
# Filter by HTTP method
|
||||||
|
wrangler tail --method POST
|
||||||
|
|
||||||
|
# Filter by IP
|
||||||
|
wrangler tail --ip-address 1.2.3.4
|
||||||
|
|
||||||
|
# Format as JSON
|
||||||
|
wrangler tail --format json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deployments
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List recent deployments
|
||||||
|
wrangler deployments list
|
||||||
|
|
||||||
|
# View specific deployment
|
||||||
|
wrangler deployments view DEPLOYMENT_ID
|
||||||
|
|
||||||
|
# Rollback to previous deployment
|
||||||
|
wrangler rollback --deployment-id DEPLOYMENT_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
### Secrets
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Set secret (interactive)
|
||||||
|
wrangler secret put MY_SECRET
|
||||||
|
|
||||||
|
# Set secret from file
|
||||||
|
echo "secret-value" | wrangler secret put MY_SECRET
|
||||||
|
|
||||||
|
# List secrets
|
||||||
|
wrangler secret list
|
||||||
|
|
||||||
|
# Delete secret
|
||||||
|
wrangler secret delete MY_SECRET
|
||||||
|
```
|
||||||
|
|
||||||
|
### KV Operations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create KV namespace
|
||||||
|
wrangler kv namespace create MY_KV
|
||||||
|
|
||||||
|
# List namespaces
|
||||||
|
wrangler kv namespace list
|
||||||
|
|
||||||
|
# Put key-value
|
||||||
|
wrangler kv key put --namespace-id=YOUR_ID "key" "value"
|
||||||
|
|
||||||
|
# Get value
|
||||||
|
wrangler kv key get --namespace-id=YOUR_ID "key"
|
||||||
|
|
||||||
|
# List keys
|
||||||
|
wrangler kv key list --namespace-id=YOUR_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
### D1 Operations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create D1 database
|
||||||
|
wrangler d1 create my-database
|
||||||
|
|
||||||
|
# Execute SQL
|
||||||
|
wrangler d1 execute my-database --command "SELECT * FROM users"
|
||||||
|
|
||||||
|
# Run SQL file
|
||||||
|
wrangler d1 execute my-database --file schema.sql
|
||||||
|
|
||||||
|
# List databases
|
||||||
|
wrangler d1 list
|
||||||
|
```
|
||||||
|
|
||||||
|
### R2 Operations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Create R2 bucket
|
||||||
|
wrangler r2 bucket create my-bucket
|
||||||
|
|
||||||
|
# List buckets
|
||||||
|
wrangler r2 bucket list
|
||||||
|
|
||||||
|
# Upload file
|
||||||
|
wrangler r2 object put my-bucket/file.txt --file local-file.txt
|
||||||
|
|
||||||
|
# Download file
|
||||||
|
wrangler r2 object get my-bucket/file.txt --file local-file.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment Configuration
|
||||||
|
|
||||||
|
### Single Environment (Default)
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"name": "my-worker",
|
||||||
|
"account_id": "YOUR_ACCOUNT_ID",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"compatibility_date": "2025-10-11",
|
||||||
|
"vars": {
|
||||||
|
"ENV": "production"
|
||||||
|
},
|
||||||
|
"kv_namespaces": [
|
||||||
|
{ "binding": "MY_KV", "id": "production-kv-id" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple Environments
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"name": "my-worker",
|
||||||
|
"account_id": "YOUR_ACCOUNT_ID",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"compatibility_date": "2025-10-11",
|
||||||
|
|
||||||
|
// Shared configuration
|
||||||
|
"observability": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
|
||||||
|
// Environment-specific configuration
|
||||||
|
"env": {
|
||||||
|
"staging": {
|
||||||
|
"name": "my-worker-staging",
|
||||||
|
"vars": {
|
||||||
|
"ENV": "staging",
|
||||||
|
"API_URL": "https://api-staging.example.com"
|
||||||
|
},
|
||||||
|
"kv_namespaces": [
|
||||||
|
{ "binding": "MY_KV", "id": "staging-kv-id" }
|
||||||
|
],
|
||||||
|
"d1_databases": [
|
||||||
|
{ "binding": "DB", "database_name": "my-db-staging", "database_id": "staging-db-id" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
"production": {
|
||||||
|
"name": "my-worker-production",
|
||||||
|
"vars": {
|
||||||
|
"ENV": "production",
|
||||||
|
"API_URL": "https://api.example.com"
|
||||||
|
},
|
||||||
|
"kv_namespaces": [
|
||||||
|
{ "binding": "MY_KV", "id": "production-kv-id" }
|
||||||
|
],
|
||||||
|
"d1_databases": [
|
||||||
|
{ "binding": "DB", "database_name": "my-db", "database_id": "production-db-id" }
|
||||||
|
],
|
||||||
|
"routes": [
|
||||||
|
{ "pattern": "example.com/*", "zone_name": "example.com" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploy:
|
||||||
|
```bash
|
||||||
|
wrangler deploy --env staging
|
||||||
|
wrangler deploy --env production
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Detection in Code
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.get('/api/info', (c) => {
|
||||||
|
const env = c.env.ENV || 'development'
|
||||||
|
const apiUrl = c.env.API_URL || 'http://localhost:3000'
|
||||||
|
|
||||||
|
return c.json({ env, apiUrl })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CI/CD Pipelines
|
||||||
|
|
||||||
|
### GitHub Actions
|
||||||
|
|
||||||
|
Create `.github/workflows/deploy.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Deploy to Cloudflare Workers
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Deploy
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: npm test
|
||||||
|
|
||||||
|
- name: Deploy to Cloudflare Workers
|
||||||
|
uses: cloudflare/wrangler-action@v3
|
||||||
|
with:
|
||||||
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
```
|
||||||
|
|
||||||
|
**With environment-specific deployment**:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- staging
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Deploy to staging
|
||||||
|
if: github.ref == 'refs/heads/staging'
|
||||||
|
uses: cloudflare/wrangler-action@v3
|
||||||
|
with:
|
||||||
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
command: deploy --env staging
|
||||||
|
|
||||||
|
- name: Deploy to production
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
uses: cloudflare/wrangler-action@v3
|
||||||
|
with:
|
||||||
|
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||||
|
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||||
|
command: deploy --env production
|
||||||
|
```
|
||||||
|
|
||||||
|
**Add secrets to GitHub**:
|
||||||
|
1. Go to: Repository → Settings → Secrets → Actions
|
||||||
|
2. Add `CLOUDFLARE_API_TOKEN`
|
||||||
|
3. Add `CLOUDFLARE_ACCOUNT_ID`
|
||||||
|
|
||||||
|
### GitLab CI/CD
|
||||||
|
|
||||||
|
Create `.gitlab-ci.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
image: node:20
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- test
|
||||||
|
- deploy
|
||||||
|
|
||||||
|
test:
|
||||||
|
stage: test
|
||||||
|
script:
|
||||||
|
- npm ci
|
||||||
|
- npm test
|
||||||
|
|
||||||
|
deploy_staging:
|
||||||
|
stage: deploy
|
||||||
|
script:
|
||||||
|
- npm ci
|
||||||
|
- npx wrangler deploy --env staging
|
||||||
|
only:
|
||||||
|
- staging
|
||||||
|
environment:
|
||||||
|
name: staging
|
||||||
|
|
||||||
|
deploy_production:
|
||||||
|
stage: deploy
|
||||||
|
script:
|
||||||
|
- npm ci
|
||||||
|
- npx wrangler deploy --env production
|
||||||
|
only:
|
||||||
|
- main
|
||||||
|
environment:
|
||||||
|
name: production
|
||||||
|
```
|
||||||
|
|
||||||
|
**Add variables to GitLab**:
|
||||||
|
1. Go to: Settings → CI/CD → Variables
|
||||||
|
2. Add `CLOUDFLARE_API_TOKEN` (masked)
|
||||||
|
3. Add `CLOUDFLARE_ACCOUNT_ID`
|
||||||
|
|
||||||
|
### Manual Deployment Script
|
||||||
|
|
||||||
|
Create `scripts/deploy.sh`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
ENV=${1:-production}
|
||||||
|
|
||||||
|
echo "🚀 Deploying to $ENV..."
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
echo "Running tests..."
|
||||||
|
npm test
|
||||||
|
|
||||||
|
# Type check
|
||||||
|
echo "Type checking..."
|
||||||
|
npm run type-check
|
||||||
|
|
||||||
|
# Build
|
||||||
|
echo "Building..."
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Deploy
|
||||||
|
echo "Deploying to Cloudflare..."
|
||||||
|
if [ "$ENV" = "production" ]; then
|
||||||
|
wrangler deploy --env production
|
||||||
|
else
|
||||||
|
wrangler deploy --env staging
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✅ Deployment complete!"
|
||||||
|
echo "🔗 Check logs: wrangler tail --env $ENV"
|
||||||
|
```
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
```bash
|
||||||
|
chmod +x scripts/deploy.sh
|
||||||
|
./scripts/deploy.sh staging
|
||||||
|
./scripts/deploy.sh production
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Production Best Practices
|
||||||
|
|
||||||
|
### 1. Use Compatibility Dates
|
||||||
|
|
||||||
|
Always set a recent `compatibility_date`:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"compatibility_date": "2025-10-11"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Ensures consistent behavior and access to new features.
|
||||||
|
|
||||||
|
**Update regularly**: Check https://developers.cloudflare.com/workers/configuration/compatibility-dates/
|
||||||
|
|
||||||
|
### 2. Enable Observability
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"observability": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Provides**:
|
||||||
|
- Real-time metrics
|
||||||
|
- Error tracking
|
||||||
|
- Performance monitoring
|
||||||
|
|
||||||
|
### 3. Set Resource Limits
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"limits": {
|
||||||
|
"cpu_ms": 50 // Maximum CPU time per request (paid plan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Configure Custom Domains
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"pattern": "api.example.com/*",
|
||||||
|
"zone_name": "example.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Or via dashboard**:
|
||||||
|
1. Workers & Pages → Your Worker → Triggers
|
||||||
|
2. Add Custom Domain
|
||||||
|
|
||||||
|
### 5. Use Secrets for Sensitive Data
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Never commit secrets to git
|
||||||
|
wrangler secret put API_KEY
|
||||||
|
wrangler secret put DATABASE_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Access in code
|
||||||
|
const apiKey = c.env.API_KEY
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Implement Rate Limiting
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
|
||||||
|
const app = new Hono()
|
||||||
|
|
||||||
|
app.use('/api/*', async (c, next) => {
|
||||||
|
const ip = c.req.header('cf-connecting-ip')
|
||||||
|
const key = `rate-limit:${ip}`
|
||||||
|
|
||||||
|
const count = await c.env.MY_KV.get(key)
|
||||||
|
if (count && parseInt(count) > 100) {
|
||||||
|
return c.json({ error: 'Rate limit exceeded' }, 429)
|
||||||
|
}
|
||||||
|
|
||||||
|
await c.env.MY_KV.put(key, (parseInt(count || '0') + 1).toString(), {
|
||||||
|
expirationTtl: 60 // 1 minute
|
||||||
|
})
|
||||||
|
|
||||||
|
await next()
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Add Health Check Endpoint
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.get('/health', (c) => {
|
||||||
|
return c.json({
|
||||||
|
status: 'ok',
|
||||||
|
version: '1.0.0',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. Implement Error Tracking
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.onError((err, c) => {
|
||||||
|
console.error('Error:', err)
|
||||||
|
|
||||||
|
// Send to error tracking service
|
||||||
|
// await sendToSentry(err)
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
error: 'Internal Server Error',
|
||||||
|
requestId: c.req.header('cf-ray')
|
||||||
|
}, 500)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. Use Structured Logging
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { logger } from 'hono/logger'
|
||||||
|
|
||||||
|
app.use('*', logger())
|
||||||
|
|
||||||
|
app.get('/api/users', (c) => {
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
level: 'info',
|
||||||
|
message: 'Fetching users',
|
||||||
|
userId: c.req.header('x-user-id'),
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
}))
|
||||||
|
|
||||||
|
return c.json({ users: [] })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10. Test Before Deploying
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run tests
|
||||||
|
npm test
|
||||||
|
|
||||||
|
# Type check
|
||||||
|
npm run type-check
|
||||||
|
|
||||||
|
# Lint
|
||||||
|
npm run lint
|
||||||
|
|
||||||
|
# Test locally
|
||||||
|
wrangler dev --local
|
||||||
|
|
||||||
|
# Test remotely (without deploying)
|
||||||
|
wrangler dev
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Monitoring and Logs
|
||||||
|
|
||||||
|
### Real-Time Logs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Tail all requests
|
||||||
|
wrangler tail
|
||||||
|
|
||||||
|
# Filter by status
|
||||||
|
wrangler tail --status error
|
||||||
|
wrangler tail --status ok
|
||||||
|
|
||||||
|
# Filter by method
|
||||||
|
wrangler tail --method POST
|
||||||
|
|
||||||
|
# Filter by search term
|
||||||
|
wrangler tail --search "error"
|
||||||
|
|
||||||
|
# Output as JSON
|
||||||
|
wrangler tail --format json
|
||||||
|
```
|
||||||
|
|
||||||
|
### Analytics Dashboard
|
||||||
|
|
||||||
|
View in Cloudflare Dashboard:
|
||||||
|
1. Workers & Pages → Your Worker → Metrics
|
||||||
|
2. See:
|
||||||
|
- Requests per second
|
||||||
|
- Errors
|
||||||
|
- CPU time
|
||||||
|
- Response time
|
||||||
|
|
||||||
|
### Custom Metrics
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.use('*', async (c, next) => {
|
||||||
|
const start = Date.now()
|
||||||
|
|
||||||
|
await next()
|
||||||
|
|
||||||
|
const duration = Date.now() - start
|
||||||
|
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
type: 'metric',
|
||||||
|
name: 'request_duration',
|
||||||
|
value: duration,
|
||||||
|
path: c.req.path,
|
||||||
|
method: c.req.method,
|
||||||
|
status: c.res.status
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### External Monitoring
|
||||||
|
|
||||||
|
**Use Workers Analytics Engine**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.use('*', async (c, next) => {
|
||||||
|
await next()
|
||||||
|
|
||||||
|
// Write to Analytics Engine
|
||||||
|
c.env.ANALYTICS.writeDataPoint({
|
||||||
|
indexes: [c.req.path],
|
||||||
|
blobs: [c.req.method, c.req.header('user-agent')],
|
||||||
|
doubles: [Date.now(), c.res.status]
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Or send to external services**:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Send to Datadog, New Relic, etc.
|
||||||
|
await fetch('https://api.datadoghq.com/api/v1/logs', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'DD-API-KEY': c.env.DATADOG_API_KEY,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
message: 'Request processed',
|
||||||
|
status: c.res.status,
|
||||||
|
path: c.req.path
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rollback Strategy
|
||||||
|
|
||||||
|
### Immediate Rollback
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List recent deployments
|
||||||
|
wrangler deployments list
|
||||||
|
|
||||||
|
# Rollback to specific deployment
|
||||||
|
wrangler rollback --deployment-id DEPLOYMENT_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gradual Rollout
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"name": "my-worker-canary",
|
||||||
|
"routes": [
|
||||||
|
{
|
||||||
|
"pattern": "example.com/*",
|
||||||
|
"zone_name": "example.com",
|
||||||
|
"script": "my-worker"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Deploy new version to `-canary` worker
|
||||||
|
2. Route 10% of traffic to canary
|
||||||
|
3. Monitor metrics
|
||||||
|
4. Gradually increase to 100%
|
||||||
|
5. Promote canary to main
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Performance Optimization
|
||||||
|
|
||||||
|
### 1. Minimize Bundle Size
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check bundle size
|
||||||
|
wrangler deploy --dry-run --outdir=dist
|
||||||
|
|
||||||
|
# Analyze
|
||||||
|
ls -lh dist/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tips**:
|
||||||
|
- Avoid large dependencies
|
||||||
|
- Use dynamic imports for heavy modules
|
||||||
|
- Tree-shake unused code
|
||||||
|
|
||||||
|
### 2. Use Edge Caching
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.get('/api/data', async (c) => {
|
||||||
|
const cache = caches.default
|
||||||
|
const cacheKey = new Request(c.req.url, c.req.raw)
|
||||||
|
|
||||||
|
let response = await cache.match(cacheKey)
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
// Fetch data
|
||||||
|
const data = await fetchData()
|
||||||
|
response = c.json(data)
|
||||||
|
|
||||||
|
// Cache for 5 minutes
|
||||||
|
response.headers.set('Cache-Control', 'max-age=300')
|
||||||
|
c.executionCtx.waitUntil(cache.put(cacheKey, response.clone()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Optimize Database Queries
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// ❌ Bad: N+1 queries
|
||||||
|
for (const user of users) {
|
||||||
|
const posts = await c.env.DB.prepare('SELECT * FROM posts WHERE user_id = ?').bind(user.id).all()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Good: Single query
|
||||||
|
const posts = await c.env.DB.prepare('SELECT * FROM posts WHERE user_id IN (?)').bind(userIds).all()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting Deployments
|
||||||
|
|
||||||
|
### Deployment Fails
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check configuration
|
||||||
|
wrangler deploy --dry-run
|
||||||
|
|
||||||
|
# Verbose output
|
||||||
|
WRANGLER_LOG=debug wrangler deploy
|
||||||
|
|
||||||
|
# Check account access
|
||||||
|
wrangler whoami
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Errors
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clear cache
|
||||||
|
rm -rf node_modules/.wrangler
|
||||||
|
rm -rf .wrangler
|
||||||
|
|
||||||
|
# Reinstall dependencies
|
||||||
|
npm ci
|
||||||
|
|
||||||
|
# Try again
|
||||||
|
npm run deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Routes Not Working
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List routes
|
||||||
|
wrangler routes list
|
||||||
|
|
||||||
|
# Check zone assignment
|
||||||
|
# Dashboard → Workers & Pages → Your Worker → Triggers
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Production-tested deployment patterns** ✅
|
||||||
|
**CI/CD examples validated** ✅
|
||||||
|
**Monitoring strategies proven** ✅
|
||||||
23
templates/package.json
Normal file
23
templates/package.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "cloudflare-worker-base-test",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"deploy": "wrangler deploy",
|
||||||
|
"dev": "wrangler dev",
|
||||||
|
"start": "wrangler dev",
|
||||||
|
"test": "vitest",
|
||||||
|
"cf-typegen": "wrangler types"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@cloudflare/vite-plugin": "^1.13.13",
|
||||||
|
"@cloudflare/vitest-pool-workers": "^0.8.19",
|
||||||
|
"typescript": "^5.5.2",
|
||||||
|
"vite": "^7.1.10",
|
||||||
|
"vitest": "~3.2.0",
|
||||||
|
"wrangler": "^4.43.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"hono": "^4.10.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
55
templates/public/index.html
Normal file
55
templates/public/index.html
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Cloudflare Worker + Hono + Static Assets</title>
|
||||||
|
<link rel="stylesheet" href="/styles.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>🔥 Cloudflare Worker + Hono + Static Assets</h1>
|
||||||
|
<p>Testing API routes with Workers Static Assets and SPA fallback</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="test-section">
|
||||||
|
<h2>API Tests</h2>
|
||||||
|
<p>These API routes are handled by the Worker thanks to <code>run_worker_first</code> configuration.</p>
|
||||||
|
|
||||||
|
<div class="test-group">
|
||||||
|
<button onclick="testHello()">Test /api/hello</button>
|
||||||
|
<button onclick="testData()">Test /api/data</button>
|
||||||
|
<button onclick="testEcho()">Test /api/echo (POST)</button>
|
||||||
|
<button onclick="testHealth()">Test /api/health</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="results">
|
||||||
|
<h3>Results:</h3>
|
||||||
|
<pre id="output">Click a button to test the API...</pre>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="info-section">
|
||||||
|
<h2>✅ What This Demonstrates</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Static Assets</strong>: This HTML is served from <code>public/</code></li>
|
||||||
|
<li><strong>API Routes</strong>: <code>/api/*</code> routes are handled by Worker first</li>
|
||||||
|
<li><strong>SPA Fallback</strong>: Unknown routes return this index.html</li>
|
||||||
|
<li><strong>Hono Framework</strong>: Type-safe routing with JSON responses</li>
|
||||||
|
<li><strong>ES Module Format</strong>: Correct export pattern prevents build errors</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>
|
||||||
|
📚 <a href="https://developers.cloudflare.com/workers/static-assets/">Static Assets Docs</a> |
|
||||||
|
<a href="https://hono.dev/docs/getting-started/cloudflare-workers">Hono Docs</a> |
|
||||||
|
<a href="https://developers.cloudflare.com/workers/vite-plugin/">Vite Plugin Docs</a>
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
83
templates/public/script.js
Normal file
83
templates/public/script.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* API Test Functions
|
||||||
|
*
|
||||||
|
* These functions call the Worker API routes and display the results.
|
||||||
|
* Notice how API routes work seamlessly with static assets thanks to
|
||||||
|
* the "run_worker_first" configuration in wrangler.jsonc
|
||||||
|
*/
|
||||||
|
|
||||||
|
const output = document.getElementById('output')
|
||||||
|
|
||||||
|
function displayResult(data, status = 200) {
|
||||||
|
const formatted = JSON.stringify(data, null, 2)
|
||||||
|
output.textContent = `Status: ${status}\n\n${formatted}`
|
||||||
|
output.style.borderLeft = status === 200 ? '4px solid #4caf50' : '4px solid #f44336'
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayError(error) {
|
||||||
|
output.textContent = `Error: ${error.message}\n\nCheck console for details.`
|
||||||
|
output.style.borderLeft = '4px solid #f44336'
|
||||||
|
console.error('API Error:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testHello() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/hello')
|
||||||
|
const data = await response.json()
|
||||||
|
displayResult(data, response.status)
|
||||||
|
} catch (error) {
|
||||||
|
displayError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testData() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/data')
|
||||||
|
const data = await response.json()
|
||||||
|
displayResult(data, response.status)
|
||||||
|
} catch (error) {
|
||||||
|
displayError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testEcho() {
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
test: 'data',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
random: Math.random(),
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch('/api/echo', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
displayResult(data, response.status)
|
||||||
|
} catch (error) {
|
||||||
|
displayError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function testHealth() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/health')
|
||||||
|
const data = await response.json()
|
||||||
|
displayResult(data, response.status)
|
||||||
|
} catch (error) {
|
||||||
|
displayError(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display welcome message on load
|
||||||
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
|
displayResult({
|
||||||
|
message: 'Welcome! Click a button above to test the API.',
|
||||||
|
info: 'All API routes are handled by the Cloudflare Worker',
|
||||||
|
static_assets: 'This HTML/CSS/JS is served from public/ directory',
|
||||||
|
})
|
||||||
|
})
|
||||||
178
templates/public/styles.css
Normal file
178
templates/public/styles.css
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
min-height: 100vh;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 3rem 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header p {
|
||||||
|
opacity: 0.9;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #667eea;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: #555;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-section {
|
||||||
|
border-bottom: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: #667eea;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: #5568d3;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#results {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#output {
|
||||||
|
background: #1e1e1e;
|
||||||
|
color: #d4d4d4;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-family: 'Monaco', 'Courier New', monospace;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section ul {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section li {
|
||||||
|
padding: 0.75rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-left: 4px solid #667eea;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-section strong {
|
||||||
|
color: #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: #e8eaf6;
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Monaco', 'Courier New', monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #5e35b1;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
text-align: center;
|
||||||
|
border-top: 1px solid #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: #667eea;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
body {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
padding: 2rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
header h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.test-group {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
84
templates/src/index.ts
Normal file
84
templates/src/index.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* Cloudflare Worker with Hono
|
||||||
|
*
|
||||||
|
* CRITICAL: Export pattern to prevent "Cannot read properties of undefined (reading 'map')" error
|
||||||
|
* See: https://github.com/honojs/hono/issues/3955
|
||||||
|
*
|
||||||
|
* ✅ CORRECT: export default app (for Hono apps)
|
||||||
|
* ❌ WRONG: export default { fetch: app.fetch } (causes build errors with Vite)
|
||||||
|
*
|
||||||
|
* Exception: If you need multiple handlers (scheduled, tail, etc.), use Module Worker format:
|
||||||
|
* export default {
|
||||||
|
* fetch: app.fetch,
|
||||||
|
* scheduled: async (event, env, ctx) => { ... }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
|
||||||
|
// Type-safe environment bindings
|
||||||
|
type Bindings = {
|
||||||
|
ASSETS: Fetcher
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = new Hono<{ Bindings: Bindings }>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Routes
|
||||||
|
*
|
||||||
|
* These routes are handled by the Worker BEFORE static assets due to
|
||||||
|
* "run_worker_first": ["/api/*"] in wrangler.jsonc
|
||||||
|
*/
|
||||||
|
app.get('/api/hello', (c) => {
|
||||||
|
return c.json({
|
||||||
|
message: 'Hello from Cloudflare Workers!',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
app.get('/api/data', (c) => {
|
||||||
|
return c.json({
|
||||||
|
items: [
|
||||||
|
{ id: 1, name: 'Item 1', description: 'First item' },
|
||||||
|
{ id: 2, name: 'Item 2', description: 'Second item' },
|
||||||
|
{ id: 3, name: 'Item 3', description: 'Third item' },
|
||||||
|
],
|
||||||
|
count: 3,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
app.post('/api/echo', async (c) => {
|
||||||
|
const body = await c.req.json()
|
||||||
|
return c.json({
|
||||||
|
received: body,
|
||||||
|
method: c.req.method,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Health check endpoint
|
||||||
|
*/
|
||||||
|
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 will be served from the public/ directory
|
||||||
|
* thanks to Workers Static Assets
|
||||||
|
*/
|
||||||
|
app.all('*', (c) => {
|
||||||
|
// Let Cloudflare Workers handle static assets automatically
|
||||||
|
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
|
||||||
45
templates/tsconfig.json
Normal file
45
templates/tsconfig.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
|
||||||
|
/* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
|
"target": "es2021",
|
||||||
|
/* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
"lib": ["es2021"],
|
||||||
|
/* Specify what JSX code is generated. */
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Specify what module code is generated. */
|
||||||
|
"module": "es2022",
|
||||||
|
/* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
/* Enable importing .json files */
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
|
||||||
|
/* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
|
||||||
|
"allowJs": true,
|
||||||
|
/* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
"checkJs": false,
|
||||||
|
|
||||||
|
/* Disable emitting files from a compilation. */
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
"isolatedModules": true,
|
||||||
|
/* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
/* Ensure that casing is correct in imports. */
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
|
||||||
|
/* Enable all strict type-checking options. */
|
||||||
|
"strict": true,
|
||||||
|
|
||||||
|
/* Skip type checking all .d.ts files. */
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"types": [
|
||||||
|
"./worker-configuration.d.ts"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"exclude": ["test"],
|
||||||
|
"include": ["worker-configuration.d.ts", "src/**/*.ts"]
|
||||||
|
}
|
||||||
11
templates/vite.config.ts
Normal file
11
templates/vite.config.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import { cloudflare } from '@cloudflare/vite-plugin'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
cloudflare({
|
||||||
|
// Optional: Configure the plugin if needed
|
||||||
|
// See: https://developers.cloudflare.com/workers/vite-plugin/
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
52
templates/wrangler.jsonc
Normal file
52
templates/wrangler.jsonc
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* For more details on how to configure Wrangler, refer to:
|
||||||
|
* https://developers.cloudflare.com/workers/wrangler/configuration/
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
"$schema": "node_modules/wrangler/config-schema.json",
|
||||||
|
"name": "cloudflare-worker-base-test",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"account_id": "0460574641fdbb98159c98ebf593e2bd",
|
||||||
|
"compatibility_date": "2025-10-11",
|
||||||
|
"observability": {
|
||||||
|
"enabled": true
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Static Assets
|
||||||
|
* https://developers.cloudflare.com/workers/static-assets/
|
||||||
|
*
|
||||||
|
* CRITICAL: run_worker_first prevents SPA fallback from intercepting API routes
|
||||||
|
* See: https://github.com/cloudflare/workers-sdk/issues/8879
|
||||||
|
*/
|
||||||
|
"assets": {
|
||||||
|
"directory": "./public/",
|
||||||
|
"binding": "ASSETS",
|
||||||
|
"not_found_handling": "single-page-application",
|
||||||
|
"run_worker_first": ["/api/*"]
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Smart Placement
|
||||||
|
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
|
||||||
|
*/
|
||||||
|
// "placement": { "mode": "smart" }
|
||||||
|
/**
|
||||||
|
* Bindings
|
||||||
|
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
|
||||||
|
* databases, object storage, AI inference, real-time communication and more.
|
||||||
|
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Environment Variables
|
||||||
|
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
|
||||||
|
*/
|
||||||
|
// "vars": { "MY_VARIABLE": "production_value" }
|
||||||
|
/**
|
||||||
|
* Note: Use secrets to store sensitive data.
|
||||||
|
* https://developers.cloudflare.com/workers/configuration/secrets/
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Service Bindings (communicate between multiple Workers)
|
||||||
|
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
|
||||||
|
*/
|
||||||
|
// "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user