Skip to content

Latest commit

History

56 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

StatusBeam

CIQuality Gate StatuscodecovLicense: MITReviewed by GreptileReviewed by cubicLive demoDeploy on Cloudflare

An open-source, CDN-native status page generator — a modern take on upptime.

🔗 Live demo:demo.statusbeam.dev — a StatusBeam instance monitoring a few public services, running on Cloudflare.

StatusBeam monitors your services, records their uptime as durable time-series data, and publishes a fast, good-looking status page to the edge. It keeps the parts of upptime that people love — config-as-YAML, zero servers to babysit, badges, a public JSON API — while fixing upptime's biggest structural weaknesses:

  • No client-side rate limits. upptime's page calls the GitHub API from the visitor's browser (unauthenticated, 60 req/h/IP), so popular pages break with a "rate limit exceeded" screen. StatusBeam renders every byte at the edge from its own store — the browser never talks to a third-party API.
  • Reliable scheduling. upptime rides GitHub Actions cron, which is best-effort (a "every 5 min" job can slip to 15–60 min). StatusBeam uses Cloudflare Cron Triggers, which fire on time.
  • A store that scales. upptime treats git commit history as its database and walks it through a rate-limited API. StatusBeam uses Cloudflare D1 + KV, purpose-built for time-series reads.
  • A current, maintained frontend. upptime's page is built on Svelte 3 + Sapper, both end-of-life. StatusBeam is Astro + shadcn/ui.

Status

🚧 Active development. The core pipeline is live end-to-end — HTTP checks on Cloudflare Cron write to D1/KV, the Astro page renders 90-day uptime bars, response-time charts, and an incident timeline at the edge, and status changes fan out to Slack/webhooks while purging the edge cache. A live demo runs on Cloudflare. TCP/SSL checks, a public API + badges, and more notification channels are next — see the Roadmap.


How it works

StatusBeam is deliberately split into three independent layers. Each can be understood, deployed, and replaced on its own.

 ┌──────────────────────────────────────────────────────────────┐
│ 1. CHECK LAYER — Cloudflare Cron Worker │
│ • Cron Triggers ping every configured service on schedule │
│ • Derives up / degraded / down from status + response time │
│ • Writes time-series to D1, current snapshot to KV │
│ • On a status change: enqueue a notification event, and │
│ purge the page/badge cache by tag (ctx.cache.purge) │
└──────────┬────────────────┬─────────────────────┬────────────-┘
│ writes │ enqueues │ purges on change
▼ ▼ │
┌───────────────────┐ ┌──────────────────────┐ │
│ D1 (time-series, │ │ 2. NOTIFY LAYER — │ │
│ incidents) │ │ Queue consumer │ │
│ KV (current │ │ • Email, Slack, │ │
│ snapshot) │ │ webhook, RSS/Atom │ │
└─────────┬─────────┘ └──────────────────────┘ │
│ reads at the edge │
▼ ▼
┌──────────────────────────────────────────────────────────────┐
│ 3. DISPLAY LAYER — Astro site on Cloudflare │
│ • Renders the page at the edge from D1/KV (no browser → │
│ third-party API calls, so no client rate limits) │
│ • Fronted by Workers Cache (tiered edge cache): renders │
│ set Cache-Control + stale-while-revalidate, hits skip │
│ the Worker + D1, concurrent requests collapse; the check │
│ layer purges by tag on change, so updates are near- │
│ instant, not TTL-bound │
│ • shadcn/ui via React islands for the interactive bits │
│ (charts, time-range filters); everything else ships 0 JS │
│ • Emits shields.io-compatible badge JSON + a public API │
└──────────────────────────────────────────────────────────────┘

Because the check and display layers live on Cloudflare — not on the infrastructure being monitored — your status page stays up even when your own services are down. That resilience is the whole point of a status page.


Tech stack

LayerChoiceWhy
FrontendAstro 7Static-first (ideal for a mostly-read page), ~0 KB JS by default, a Cloudflare first-party framework (acquired Jan 2026) with workerd dev/prod parity, a Rust compiler (15–61% faster builds), and a stable Astro.cache route-caching API plus an experimental cacheCloudflare() provider for Workers Cache.
UI componentsshadcn/ui (React islands) + Tailwind CSSCopy-in-your-repo components you own and can fork — perfect for OSS. Used natively via Astro's React islands; hydrated only where interactivity is needed.
Chartsshadcn/ui charts (Recharts)Response-time graphs, themed and dark-mode-ready out of the box.
Check schedulerCloudflare Cron Triggers (Worker)On-time execution, unlike GitHub Actions cron.
Data storeCloudflare D1 (SQLite) + KVD1 for time-series & incident history; KV for the current snapshot.
Edge cacheCloudflare Workers CacheTiered cache in front of the Astro Worker: Cache-Control + stale-while-revalidate, request collapsing, and tag-based purge on status change — near-instant updates without hammering D1. Today via Cache-Control headers; Astro 7's Astro.cache / cacheCloudflare() is the forward path.
NotificationsCloudflare Workers + QueuesEmail / Slack / webhook / RSS on status change, decoupled from the UI.
Deploy targetCloudflare (primary) · Vercel (supported)Astro adapters target both; Cloudflare is the native, batteries-included path.
ToolingBun · Wrangler · TypeScriptBun for install/scripts; Wrangler for Worker + D1 + KV.

The full rationale — including why Astro over SvelteKit and TanStack Start, and why a Cron Worker over GitHub Actions — is in docs/adr/0001-tech-stack.md.


Design

The UI follows the information architecture proven by Statuspage.io and the modern, static-first aesthetic of Instatus:

  • Overall-status banner — one calm, unambiguous line ("All Systems Operational") in a single color that rolls up the worst component state.
  • Component rows — one per service, grouped and collapsible, each with a status pill: Operational / Degraded / Partial Outage / Major Outage / Maintenance.
  • 90-day uptime bars — the signature timeline: one colored bar per day, hover for date + uptime % + linked incidents, gray for no-data days. Adaptive intervals (Instatus-style) let the same component render other windows.
  • Incident timeline — a reverse-chronological, date-grouped feed; each incident threads timestamped updates through the Investigating → Identified → Monitoring → Resolved lifecycle. Scheduled maintenance is a distinct, forward-looking entry.
  • One severity token system — five states as CSS variables (light + dark, OKLCH), driving the banner, pills, and bars from a single source of truth. Dark mode ships by default. Color is paired with icon + text for accessibility.

Project layout

A Bun-workspaces monorepo. The three runtime layers map to three workspaces, with the domain logic shared in core:

statusbeam/
├── apps/
│ ├── web/ # Astro status page (Cloudflare adapter + Workers Cache)
│ └── worker/ # Cron Worker: checks + notifications (D1/KV, schema.sql)
├── packages/
│ └── core/ # shared config schema (zod), types, status derivation
├── status.config.example.yml
├── mise.toml # pinned toolchain (node, bun)
└── orca.yaml # worktree setup

Local development:

mise install # pinned node + bun
bun install # install workspaces
bun run test# core unit tests (bun:test)
bun run dev # Astro dev server (renders sample data without bindings)

Configuration

A single YAML file is the only thing you edit — the same idea as upptime's .upptimerc.yml. Copy status.config.example.yml to status.config.yml:

# status.config.ymlname: Acme Statussites:
- name: Websiteurl: https://example.comcheck: http # http | tcp | ssl | statuspage | incidentioexpectedStatusCodes: [200]maxResponseTime: 2000# ms → "degraded" above this
- name: APIurl: https://api.example.com/healthcheck: http
- name: Claude # mirror an Atlassian Statuspage (status.claude.com, *.statuspage.io, …)url: https://status.claude.com # base URL; /api/v2/summary.json is appended for youcheck: statuspage
- name: Claude API # or track one service on that page by component name/idurl: https://status.claude.comcheck: statuspagecomponent: Claude API (api.anthropic.com)
- name: OpenAI # incident.io status pages read the same way (Statuspage-compatible)url: https://status.openai.comcheck: incidentionotifications: # all optional; keep the real Slack URL (a secret) in your KV configslack:
webhookUrl: https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXwebhooks:
- url: https://example.com/status-hooktheme:
logoUrl: /logo.svgdarkMode: truelocale: en # fallback UI language: en | zh | ja | ko (default en)

Check types

Each site sets a check kind:

checkWhat it does
httpFetches url; up/degraded/down from the status code and response time.
tcpReserved — currently falls through to http (roadmap).
sslReserved — currently falls through to http (roadmap).
statuspageMirrors an Atlassian Statuspage's own verdict. See the Statuspage adapter guide.
incidentioMirrors an incident.io status page (Statuspage-compatible). See the incident.io adapter guide.
sentryMirrors a Sentry Uptime monitor via issue webhook (real-time) + optional poll backstop. See the Sentry adapter guide.

The Statuspage adapter reads a vendor's /api/v2/summary.json (Claude, Vercel, *.statuspage.io, …) and maps their overall indicator — or a single component you name — to a status. Full reference, status-mapping tables, and edge behavior: docs/adapters/statuspage.md.

The incident.io adapter reads the same Statuspage-compatible /api/v2/summary.json that incident.io status pages (status.openai.com, status.incident.io, …) serve — so incidentio and statuspage behave identically; use whichever names your vendor: docs/adapters/incidentio.md.

The Sentry adapter presents a Sentry Uptime monitor on your status page. Sentry runs the checks; StatusBeam ingests the verdict in real time from a Sentry issue webhook (POST /webhooks/sentry/:slug), with an optional cron poll of Sentry's Issues API as the backstop. Binary (up/down). Setup, status mapping, and the webhook-only vs. poll modes: docs/adapters/sentry.md.

Internationalization

The status page UI is translated into English (en), Simplified Chinese (zh), Japanese (ja), and Korean (ko); dates and relative times localize automatically.

Each language is a URL prefix — /en/, /ja/, /ko/, /zh/ — so every language is cached independently at the edge (no cache fragmentation). Visiting the bare / redirects to the visitor's language, chosen in this order:

  1. their remembered choice (a locale cookie, set when they pick a language),
  2. their browser's Accept-Language,
  3. the deployment's theme.locale (used only when the above don't match a supported language),
  4. English.

A language switcher in the footer lets visitors change and remember their choice.


Badges & public API

Every deployment exposes a small public JSON surface at /api/*, served from the same edge-cached KV snapshot as the status page (and purged on the same status changes, so badges never lag the page).

Badges

The badge routes speak the shields.io endpoint protocol — point shields.io at one and it renders the SVG; StatusBeam only emits the JSON. Replace <origin> with your status page's URL and <slug> with a component's slug (the slug from status.config.yml, or the slugified name):

BadgeEndpoint
Overall status/api/badge.json
Site status/api/badge/<slug>.json
Site uptime/api/badge/<slug>/uptime.json (?period=day|week|month, default month)
Response time/api/badge/<slug>/response-time.json
![status](https://img.shields.io/endpoint?url=https://status.example.com/api/badge.json)![uptime](https://img.shields.io/endpoint?url=https://status.example.com/api/badge/api/uptime.json)

Colors are derived from severity (green → operational, yellow → degraded, red → down), uptime ratio, and response time. Add any shields.io query (?style=flat-square, ?label=API, ?logo=cloudflare) to restyle the rendered badge.

Status API

  • GET /api/status.json — the whole dashboard: rolled-up status plus a lean per-site summary (status, response time, day/week/month uptime).
  • GET /api/status/<slug>.json — one site's full record, including the 90-day history and response-time samples.

Both send Access-Control-Allow-Origin: *, so a browser can fetch them directly.


Deployment

StatusBeam deploys to any Cloudflare account (Workers + D1 + KV + Pages). Vercel is also supported for the display layer via Astro's Vercel adapter.

You deploy StatusBeam as a package, not a fork (ADR-0002): your repo holds only your config, and the app is a versioned dependency. Scaffold a thin project, then let the statusbeam CLI provision D1 + KV, wire your custom domain and cron, apply the schema, upload status.config.yml, and deploy both Workers — idempotent, safe to re-run.

bunx create-statusbeam my-status # or "Use this template" on statusbeam-templatecd my-status
bunx wrangler login # or export CLOUDFLARE_API_TOKEN
bun install
bunx statusbeam setup # provisions, configures, deploys (--skip-deploy to stop before deploy)

Upgrading is bunx statusbeam update — no upstream merge. Prefer to modify the app source? You can still fork and deploy the monorepo directly; see the appendix in DEPLOYMENT.md.

Instant cache invalidation (optional)

By default the page is edge-cached for s-maxage=60, so a status change shows up within a minute. For near-instant updates, the check Worker purges the edge cache by Cache-Tag the moment a status flips. The page already emits a matching Cache-Tag response header (status-page + one status-site-<slug> per component); you just provide the Worker two secrets (purge-by-tag is available on all Cloudflare plans since April 2025):

bunx wrangler secret put CF_API_TOKEN # API token with the "Cache Purge" permission
bunx wrangler secret put CF_ZONE_ID # the zone serving your status page

When these are unset the purge is skipped (logged, not fatal) and the page simply refreshes on its 60s TTL.

Full runbook — the CLI, provisioning, config, secrets, CI deploy, and the fork-from-source appendix — is in DEPLOYMENT.md.


Roadmap

Shipped

  • Check layer — Cron Worker: HTTP checks, D1 time-series schema, KV snapshot.
  • Display layer — Astro site, shadcn/ui component set, severity token system.
  • Uptime bars & charts — 90-day adaptive timeline, per-component response-time graphs.
  • Incidents — lifecycle model (Investigating → Identified → Monitoring → Resolved) + timeline UI.
  • Notify layer (part 1) — Slack + generic webhook on status change, decoupled via Queues.
  • Edge cacheCache-Tag emit + purge-on-change loop between the check and display layers.
  • Badges & public APIshields.io endpoint badges + JSON status API, edge-cached.
  • Statuspage adapter — mirror any Atlassian Statuspage by page or component (guide).
  • incident.io adapter — mirror any incident.io status page by page or component (guide).
  • Statuspage webhooks — real-time ingest via POST /webhooks/statuspage/:slug, cron as the backstop (guide).
  • Sentry Uptime adapter — mirror a Sentry Uptime monitor via issue webhook (POST /webhooks/sentry/:slug) + optional Issues-API poll backstop (guide).

In progress / planned

  • TCP/SSL checks — extend the Cron Worker beyond HTTP (config + schema already accept them).
  • Scheduled maintenance — distinct, forward-looking incident entries.
  • Notify layer (part 2) — email + RSS/Atom feeds.
  • Migration guide — importing an existing .upptimerc.yml.
  • Vercel adapter path — documented alternative to Cloudflare.

Prior art & inspiration

  • upptime/upptime — the serverless-monitoring idea this project builds on.
  • Statuspage.io — the reference information architecture.
  • Instatus — static-first delivery and modern design.
  • statping/statping — a self-hosted, single-binary status server (Go) with its own monitoring engine, notifiers, and mobile app.
  • OpenStatus — open-source synthetic monitoring and status pages, with a globally distributed checker for latency-aware probing.
  • CachetHQ/Cachet — a long-standing open-source status page system (PHP/Laravel) centered on incident and component management.

Code review

Pull requests to StatusBeam are reviewed by two AI code reviewers, both free for open source:

  • Greptile — free for non-commercial MIT/Apache projects under its OSS program.
  • cubic — free for public repositories.

Reviewed by GreptileReviewed by cubic


License

MIT

About

Open-source, CDN-native status page generator — a modern take on upptime. Astro + shadcn/ui on Cloudflare (Cron Worker + D1/KV + Workers Cache).

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

24 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages