1. Documentation
  2. Deploying
  3. Cloudflare Workers
ReadmenpmGitHub
  • Introduction
  • Quickstart
  • Configuration file
  • Monitors
  • Checks and scheduling
  • Notifications
  • Incidents and maintenance
  • Subscribers
  • Metrics and badges
  • Identity and appearance
  • Telemetry
  • The check run
  • Uptime and history
  • Automatic incidents
  • Notification delivery
  • Public subscriptions
  • StatusPage component
  • Themes
  • UI primitives
  • Colour mode
  • React Router
  • Next.js
  • Astro
  • SQLite
  • Cloudflare D1
  • PostgreSQL
  • Schema and migrations
  • Cloudflare Workers
  • Vercel
  • Self-hosting
  • API overview
  • Status endpoints
  • Incident and maintenance endpoints
  • Subscriber endpoints
  • Badges, feeds and metrics
  • Admin endpoints
  • CLI commands
  • Package exports
  • Security
  • Troubleshooting

Cloudflare Workers

Deploy with D1, cron triggers and a Worker that owns both fetch and scheduled.

A Cloudflare deployment is a Worker that does two things: serve the framework and run the scheduler. The generator's overlay wires both, binds D1 before every request and points wrangler at the bundled migrations.

bash
bun create @heyo-sh/heyo-status my-status-page \  --template react-router --deployment cloudflare --database d1

The Worker

src/status-worker.ts
import {  cloudflareWorker,  type CloudflareExecutionContext,  type CloudflareScheduledEvent,  type D1Database,} from "@heyo-sh/heyo-status/deployments/cloudflare";type Env = { DB: D1Database; ASSETS: { fetch(request: Request): Promise<Response> } };export default {  fetch(request: Request, env: Env, context: CloudflareExecutionContext) {    (globalThis as typeof globalThis & { HEYO_STATUS_D1?: D1Database }).HEYO_STATUS_D1 = env.DB;    return frameworkHandler(request, env, context);  },  async scheduled(event: CloudflareScheduledEvent, env: Env, context: CloudflareExecutionContext) {    (globalThis as typeof globalThis & { HEYO_STATUS_D1?: D1Database }).HEYO_STATUS_D1 = env.DB;    const { statusPage } = await import("../heyo-status.config");    return cloudflareWorker(statusPage).scheduled(event, env, context);  },};

cloudflareWorker(statusPage) returns { fetch, scheduled }. The generated Worker keeps its own fetch so the framework handler can serve pages and assets, and reuses scheduled, which wraps the run in context.waitUntil().

Cron triggers

wrangler.jsonc
{  "name": "my-status-page",  "main": "src/status-worker.ts",  "compatibility_date": "2026-01-01",  "compatibility_flags": ["nodejs_compat"],  "assets": { "directory": "build/client", "binding": "ASSETS" },  "d1_databases": [    {      "binding": "DB",      "database_name": "my-status-page",      "database_id": "replace-me",      "migrations_dir": "migrations",    },  ],  "triggers": { "crons": ["*/5 * * * *", "10 0 * * *", "0 3 * * *"] },}

The three expressions must match checks.interval, checks.summaryCron and checks.cleanupCron. Derive them instead of copying:

ts
import { cloudflareCronExpressions } from "@heyo-sh/heyo-status/deployments/cloudflare";console.log(cloudflareCronExpressions(statusPage));// ["*/5 * * * *", "10 0 * * *", "0 3 * * *"]

runCloudflareScheduled() compares the incoming event.cron against the three configured expressions and runs only the matching job. An unknown trigger is deliberately a no-op, which makes stale cron configuration during a deploy harmless.

Deploy

  1. 1

    Create the database

    bash
    wrangler d1 create my-status-page

    Copy the returned database_id into wrangler.jsonc.

  2. 2

    Apply migrations

    bash
    wrangler d1 migrations apply my-status-page --localwrangler d1 migrations apply my-status-page --remote
  3. 3

    Set secrets

    bash
    wrangler secret put HEYO_STATUS_API_TOKEN

    Without it the management API answers 503 — which is the safe default, not a working configuration.

  4. 4

    Build and publish

    bash
    bun run buildbun run deploy   # wrangler deploy
  5. 5

    Verify

    bash
    curl https://status.acme.com/api/heyo-status/healthzcurl https://status.acme.com/api/heyo-status/status/summary

    Then trigger a check once so the page is not empty:

    bash
    curl -X POST https://status.acme.com/api/heyo-status/admin/checks/run \  -H "authorization: Bearer $HEYO_STATUS_API_TOKEN"

Local development

bun dev on a Cloudflare project migrates the local D1 state and builds and serves the actual Worker — it is not a framework hot-reload proxy. Re-run it after changing application code.

That is deliberate: a framework dev proxy cannot catch Worker-only startup errors, which are exactly the failures a Cloudflare deployment produces.

Platform constraints

No TCP or TLS monitors on Workers

Workers do not expose node:net or node:tls, so tcp and ssl monitors record … monitoring is unavailable in this runtime. and report down. Use http and dns, which work everywhere, and run certificate checks from a Node or Bun host if you need them.

  • nodejs_compat is required for the framework server bundle.
  • The snapshot cache is per-isolate, which absorbs bursts but is not shared.
  • Keep checks.interval within Cloudflare's cron granularity for your plan.

Related

  • Cloudflare D1
  • Scheduling
  • Troubleshooting
Schema and migrations< PreviousVercelNext >

Powered by heyo

On this page

The WorkerCron triggersDeployLocal developmentPlatform constraints