Hosting & infrastructure
vCareer runs entirely on Cloudflare — landing + docs as static Pages, the authed app as a Pages-deployed SPA, the API + cron as a single Worker, and the database as Cloudflare D1. One vendor, EU-region primary storage, no AWS or external infra in the request path.
Topology
┌──────────────────┐
│ vcareer.net │ landing + docs (Pages, static)
└──────────────────┘
│
▼
┌──────────────────┐
│ app.vcareer.net │ React SPA (Pages, static)
└──────────────────┘
│ fetch
▼
┌──────────────────┐
│ api.vcareer.net │ Hono on Workers
│ ┌────────────┐ │ - HTTP routes
│ │ scheduled │ │ - 1-min cron (pipe + tick)
│ └────────────┘ │
└──────────────────┘
│ binding
▼
┌──────────────────┐
│ D1 (SQLite) │ primary region: weur
└──────────────────┘
│
▼ outbound only
┌──────────┴──────────┐
│ VATSIM (auth + feed)│
│ aviationweather.gov │
└──────────────────────┘
Hosts
| HOST | RUNS | NOTES |
|---|---|---|
vcareer.net | Astro static (Cloudflare Pages) | Public landing + docs. |
app.vcareer.net | React + Vite SPA (Cloudflare Pages) | Authed app. Talks to api.vcareer.net. |
api.vcareer.net | Hono on Cloudflare Workers | All API routes + cron. Single Worker, multiple handlers. |
(internal) D1 | Cloudflare D1 (managed SQLite) | Application database. Worker binding, never exposed externally. |
Data flow
- Sign-in: Browser →
api.vcareer.net/auth/login→ VATSIM Connect →api.vcareer.net/auth/callback→ set session cookie → redirect toapp.vcareer.net. - Authed reads/writes:
app.vcareer.net→ fetch →api.vcareer.net/me/*with the session cookie → Hono router → Prisma → D1. - Cron tick (1-min schedule): scheduled handler →
pollAndPersist()(position pipe, 4× per minute via internal sleeps) +tickMissions()(completion verifier). - Outbound: VATSIM auth + data feed + Member API + aviationweather.gov. All public, all read-only. No data egress to third-party processors.
Scheduled work
Two independent jobs share the same Cloudflare cron invocation:
pollAndPersist()— polls the VATSIM v3 data feed every 15s (cron fires every 60s; we sleep+poll 4×). Filters to known users; writesPilotPositionSamplerows.tickMissions()— reads recent position samples + VATSIM session record for each accepted mission, decides state transitions, writesMissionCompletionLog+ updates balances.
Both are pure functions of (prisma, externalProviders) so
they're identically runnable in a local Bun process for development and
on Cloudflare Workers in prod.
Observability
- Workers Analytics — built-in dashboards for request count, errors, CPU time. Free.
- Sentry — client + Worker side, on by default in prod. Errors only; we do not capture user actions.
- Logpush — Workers logs piped to R2 or Datadog if/when log volume justifies it. Off in alpha.
Regions + residency
D1 primary region is weur (Western Europe — Frankfurt).
Read replicas may exist in other regions as part of Cloudflare's
automatic distribution; writes always go to the primary. EU
user-data residency is the default; we don't move data out of the EEA
for processing.
Cloudflare is a data processor under GDPR; we sign their standard DPA on the production account. We never expose D1 directly — every query goes through the Worker, which authenticates the session cookie before reading.
See Privacy for the user-facing summary.