Appearance
Railway
Railway is part of the intended deployment story for the merchant dashboard, but the current codebase shows a mixed reality with strong Cloudflare Worker paths. This page documents both the intended architecture and the current state, so future contributors understand why both platforms are mentioned.
Railway's Role in the Deployment Story
Railway is positioned in the strategy docs and STACK.md as the heavier compute option for the merchant dashboard — specifically for running TanStack Start's SSR server. The intended architecture was:
- Railway: Runs the full-stack app (TanStack Start SSR + Hono API). Docker deployment with Bun. Accesses Cloudflare D1 via the HTTP API for database operations.
- Cloudflare: Everything else — static sites, workers, edge services, DNS.
This split made sense because:
- TanStack Start's SSR requires a persistent Node/Bun process — something Workers don't provide
- Railway provides Docker deployment with automatic scaling and health checks
- An official Railway deployment template exists for TanStack Start
- Railway can access Cloudflare D1 via the HTTP API (external access), so the database layer stays on Cloudflare even when compute is on Railway
- The
"Run TanStack Start on Railway"template from the Railway marketplace was a known, tested deployment path
Why Both Railway and Cloudflare Exist
No single platform handles both SSR and edge deployment optimally. The two-platform strategy splits responsibilities by compute profile:
| Need | Railway | Cloudflare Workers |
|---|---|---|
| SSR rendering | Full Node/Bun process with server-side rendering | Limited CPU (10ms wall clock on free, 30ms on paid), no persistent process |
| Long-running tasks | Can run background jobs, scheduled tasks, report generation | Cron triggers limited to 15 min, no persistent state between invocations |
| Database access | D1 via HTTP API (external, ~50ms latency) | D1 via binding (internal, <10ms latency) |
| WebSocket support | Native support | Limited (requires Durable Objects, additional complexity) |
| Static assets | Served but not optimized for edge delivery | Free, edge-cached, 300+ locations, zero-config |
| Edge deployment | Single region (or multi-region with extra cost and config) | 300+ edge locations by default |
| Cost at low scale | Usage-based (~$5-20/month) | Free tier covers most needs |
| Cost at scale | Predictable, grows linearly with compute | Per-request, can spike unpredictably |
| Docker support | Native — build from Dockerfile | Not applicable (Workers use their own runtime) |
| Build pipeline | Docker build on Railway | wrangler deploy or @cloudflare/vite-plugin |
The Mixed Reality in the Codebase
The checked-in code tells a different story than the strategy docs. Understanding this gap is important for anyone reading the planning artifacts.
What the strategy docs say (STACK.md, CLAUDE.md, planning docs):
- TanStack Start + Railway for the merchant dashboard
- Full SSR with server functions
- Docker deployment on Railway
- D1 access via HTTP API
What's actually checked in (vela-dashboard/):
- Vite + React 19 + TanStack Router + TanStack Query + Hono — not TanStack Start
- Cloudflare Worker–compatible backend — Hono runs on Workers natively
- D1 accessed via binding (
env.DB), not HTTP API — the faster internal path wrangler.tomlconfigured for direct Worker deployment- No Dockerfile or Railway configuration in the repo
What this means in practice:
The dashboard can run entirely on Cloudflare Workers today. The Vite build produces static assets that Workers serve, and the Hono API routes handle all dynamic requests. This is a simpler, cheaper, and faster deployment than Railway would provide for this architecture.
The Railway path remains architecturally valid if TanStack Start SSR is implemented in the future, but it is not the current default.
The Docker Compose Staging Setup
The workspace root contains docker-compose.staging.yml, which suggests Railway (or Docker-based) deployment was explored for staging:
yaml
# docker-compose.staging.yml exists at workspace rootThis file provides a local staging environment that mimics a Railway-like Docker deployment. It's used for integration testing and staging validation before deploying to production Workers.
When to Use Which Platform
Use Cloudflare Workers When:
- The app is a Vite-built SPA + Hono API (current dashboard state)
- Static assets are served from edge
- API routes are stateless or use D1/KV bindings
- No SSR is required (client-side rendering via TanStack Query handles data loading)
- Deployment simplicity matters (single
wrangler deploy) - Service bindings are needed for inter-worker communication (admin → dashboard, checkout → dashboard)
Use Railway When:
- TanStack Start SSR is implemented and requires a persistent process
- Server functions need more than Workers' CPU time limits (30ms wall clock on paid tier)
- Background jobs or scheduled tasks exceed Workers' cron limitations (15 min max, no state)
- WebSocket connections are needed for real-time features (e.g., live billing dashboards)
- Long-running report generation or batch processing is required (e.g., monthly invoice batch)
- Complex Docker builds with native dependencies are needed
Current Default
The current default is Cloudflare Workers for everything. All 11 repos deploy to Workers:
| Repo | Deployment | Wrangler Config |
|---|---|---|
| vela-dashboard | Workers | Yes |
| vela-admin | Workers | Yes |
| vela-web | Workers | Yes |
| vela-docs | Workers | Yes |
| vela-widget | Workers | Yes |
| vela-checkout | Workers | Yes |
| vela-portal | Workers | Yes |
| vela-webhook | Workers | Yes |
| vela-synthetic | Workers (cron) | Yes |
Railway is available as an upgrade path when SSR or heavier compute becomes necessary.
Migration Path: Workers → Railway
If the dashboard ever needs to move to Railway (e.g., for TanStack Start SSR), the migration path is:
- Add Dockerfile to
vela-dashboard— Bun-based Docker image - Swap D1 access from binding (
env.DB) to HTTP API (fetch('https://api.cloudflare.com/client/v4/accounts/{id}/d1/database/{id}/query')) - Replace service bindings with internal API calls — admin, checkout, and portal would call the dashboard's Railway URL instead of using
env.DASHBOARD.fetch() - Update DNS —
app.velapay.comwould point to Railway instead of Workers - Keep static assets on Workers — the Vite build output can still be served from Workers for free
The Drizzle ORM schema and queries don't change — only the database connection method changes.
Practical Guidance for Contributors
- Don't assume Railway is running. Check
wrangler.tomlin each repo — if it exists, the service deploys to Workers. - Don't add a Dockerfile unless needed. The current Worker deployment is simpler and cheaper.
- If adding SSR via TanStack Start, evaluate whether Workers' CPU limits are sufficient before defaulting to Railway. Workers support SSR via
@cloudflare/vite-plugin. - D1 access via binding is faster than HTTP API. If running on Workers, use
env.DBbinding. If running on Railway, use D1 HTTP API with authentication. - Service bindings only work Worker-to-Worker. If the dashboard moves to Railway, the admin/checkout/portal service bindings would need to be replaced with internal API calls authenticated via
DASHBOARD_PROXY_AUTH_TOKEN. - Internal wiki docs should preserve the nuance. Don't flatten the Railway/Workers distinction. The strategy docs say Railway; the code says Workers. Both are correct for different time horizons.
Cost Comparison at Current Scale
| Cost Factor | Cloudflare Workers | Railway |
|---|---|---|
| Dashboard hosting | Free tier (100K req/day) | ~$5/month (developer plan) |
| D1 database | Free tier (5M reads/day) | Same D1, accessed via HTTP API |
| Static assets | Free (edge-cached) | Not optimized for static |
| API compute | Free tier sufficient | Billed per CPU-second |
| SSL/HTTPS | Free (Cloudflare-managed) | Free (Railway-managed) |
| Custom domain | Free (DNS on Cloudflare) | Free |
At current pre-PMF scale, Cloudflare Workers is effectively free for all workloads. Railway becomes cost-competitive only when SSR or heavy compute is required.