Skip to content

Repository files navigation

headflare

Headroom for your Cloudflare flare.

A TanStack Start app, deployed on Cloudflare Workers, that tracks month-to-date usage of your metered Cloudflare products against a custom billing reset day, projects each to end-of-cycle on a simple run-rate, and shows it as a dashboard. A daily cron also snapshots usage into D1 so you can chart how the forecast evolves across the cycle.

It answers: "will any product cross its free tier before the next reset, and how much headroom is left?" — without needing realtime data.

Stack

  • Framework: TanStack Start (file-based routing, server functions) on Cloudflare Workers, built with Vite + @cloudflare/vite-plugin.
  • UI: TailwindCSS v4 + shadcn-style components built on Base UI (@base-ui/react). Recharts for the detail charts, a dependency-free SVG sparkline on the cards.
  • State: Zustand for client UI state (basis toggle, group filter, the open metric).
  • Data: D1 (SQLite) for daily snapshots, accessed with Drizzle ORM; schema in src/db/schema.ts, migrations generated by drizzle-kit and applied via wrangler d1 migrations apply. Every Cloudflare API / D1 call happens in a TanStack Start server function (src/server/) — the browser only ever receives serialized results.

How it works

  1. The dashboard route loader calls the getLiveUsage server function.
  2. Server-side, it computes the current cycle window from RESET_DAY, builds one combined GraphQL query (one aliased block per metric in the catalogue) and posts it to the Cloudflare Analytics API.
  3. For each metric it sums month-to-date usage, projects it as mtd * (days_total / days_elapsed), and computes % of free tier + estimated overage cost.
  4. The client renders summary KPIs, per-product cards, and a detail dialog.
  5. A daily cron (src/worker.tsscheduled) and the "Collect now" button call collectAndPersist, writing daily values + a projection snapshot to D1. The detail dialog charts that snapshot history.

Architecture

src/
  worker.ts                  Custom Worker entry: TanStack fetch handler + cron `scheduled`
  router.tsx                 TanStack Start router
  routes/
    __root.tsx               HTML shell (dark theme)
    index.tsx                Dashboard route + server-side loader
  server/                    SERVER-ONLY (imports `cloudflare:workers` env)
    collector.ts             GraphQL query + projection math + D1 reads/writes (Drizzle)
    usage.ts                 createServerFn: getLiveUsage / collectNow / getMetricTrend
  db/
    schema.ts                Drizzle table definitions (source of truth)
    client.ts                getDb() — Drizzle bound to env.DB
  lib/
    metrics.ts               METRICS catalogue + cycle math + types (pure, shared)
    utils.ts                 cn()
  store/dashboard.ts         Zustand UI store
  components/
    ui/                      shadcn/ui components
    dashboard/               SummaryGrid, UsageCard, MetricDetail, controls, sparkline
drizzle/                     Generated SQL migrations (drizzle-kit)

Setup

npm install        # .npmrc sets ignore-scripts=true — see note below

# 1. Create the D1 database and paste its id into wrangler.jsonc
wrangler d1 create prod-headflare

# 2. Apply the Drizzle migrations (local + remote).
#    Migrations live in ./drizzle and are generated from src/db/schema.ts —
#    after editing the schema, run `npm run db:generate` then migrate.
npm run db:migrate:local
npm run db:migrate:remote

# 3. Set RESET_DAY in wrangler.jsonc `vars` (the billing reset day)

# 4. Set per-deployment secrets so the repo stays account-agnostic.
#    See docs/cloudflare-api-token.md for a step-by-step walkthrough.
wrangler secret put CF_ACCOUNT_ID    # your 32-hex Cloudflare account id
wrangler secret put CF_API_TOKEN     # API token, scope: Account Analytics -> Read

# 5. Regenerate binding types after editing wrangler.jsonc
npm run cf-typegen

Why .npmrc sets ignore-scripts=true: a transitive dep of miniflare (sharp, for local image-binding emulation we don't use) fails its native build on recent Node. Disabling lifecycle scripts still places every prebuilt binary the app needs (esbuild, workerd, oxide, lightningcss), so install/build just work.

Develop & deploy

npm run dev        # http://localhost:3000
npm run build      # vite build (client + Worker SSR bundle)
npm run deploy     # build + wrangler deploy

The METRICS catalogue (src/lib/metrics.ts)

Everything is config-driven — add a row per dashboard line:

{ key, label, group, node, metric, extraFilter?, included, unit, pricePerUnit? }
  • node / metric — confirm via introspection.graphql before trusting.
  • included — the free-tier allowance.
  • pricePerUnit — optional overage $/unit; drives the estimated cost.

Shipped pre-filled: Workers requests, D1 rows read/written, KV read/write/delete/list, DO compute requests. Stubbed (confirm names first): R2, Queues, Vectorize, Workers AI.

Things to know

  • Analytics ≠ billing. Cloudflare states these datasets are not the exact measure used for billing. The projection is a close operational estimate, not the invoice.
  • Workers CPU-ms isn't cleanly summable from workersInvocationsAdaptive, so it's omitted.
  • 31-day retention on the source datasets — the D1 usage_daily table is your long-term record.
  • Rate limit ~300 queries / 5 min. One daily run is negligible.

Security & privacy

headflare stores no secret or sensitive user data. Your CF_ACCOUNT_ID and CF_API_TOKEN live only as Cloudflare Worker secrets (and .dev.vars, which is gitignored) — never in the database, logs, or any committed file. The API token is read-only (Account Analytics → Read) and is only ever used server-side; it never reaches the browser. D1 stores only aggregate usage numbers and projection snapshots per metric — no credentials, request contents, or personal data. See SECURITY.md.

Contributing

PRs welcome — see CONTRIBUTING.md for setup, the metric model, and the pre-PR checks. By participating you agree to the Code of Conduct. CI runs the type-check and build on every PR.

License

MIT © headflare contributors.

About

Headroom for your Cloudflare flare

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Contributors

Languages