A full dealership stack that costs $0/month to run on Cloudflare
A storefront, staff back office, and AI-assisted tools for a car dealership — running on Cloudflare's free tier and hardened for public traffic. What that setup actually takes.
Project: Navarro Coopen →Navarro Co is a used-car dealership site: a public storefront, a "sell us your car" intake flow, and a staff back office for managing inventory and enquiries. It runs entirely on Cloudflare's free tier — one Worker, one deploy, no monthly hosting bill — and it's built to be safe to expose on the open internet, not just a demo. That combination is the whole point of this write-up: a small business doesn't need a five-figure stack to get a real, secure web presence.
Architecture
One wrangler deploy ships everything. The Worker (Hono) serves the API under /api and falls through to the static React 19 SPA for everything else. D1 (SQLite) is the single source of truth for inventory, submissions, sales, and enquiries; R2 holds car and submission photos. There are three surfaces in one app: the storefront at /, an admin console at /admin, and a mobile-app mock at /app rendered inside an iOS frame.
The export keeps Hono's methods intact instead of wrapping them, which is what lets the integration tests call the Worker directly:
That scheduled handler is a daily cron that regenerates the AI business briefing, so the admin dashboard always reads from cache and never blocks on a live model call.
AI on a budget
Workers AI shows up in three places: one-line appraisal notes on sell submissions, enquiry triage (intent, sentiment, suggested reply), and the daily briefing. The free-tier Neuron budget shaped all three: results are cached in D1, the briefing regenerates once a day on the cron, and the manual "Regenerate" button is owner-gated behind a rate limiter.
The valuation itself is deliberately not AI. The buy range is deterministic math off the pricing catalog — the model only narrates:
Numbers a staff member acts on shouldn't come out of a language model. Where no sensible fallback exists — enquiry triage — the endpoint returns a clean 503 instead of pretending.
Hardening for public launch
The hardest part wasn't features — it was making a hobby-scale project safe to expose on the open internet:
- Per-account staff logins with HMAC-signed session cookies. No shared passcode.
- Cloudflare's native rate limiting on login and public form endpoints, emulated as no-ops in local dev and tests.
- Turnstile on the sell and enquiry forms, verified server-side, and inert until both keys are configured so tests don't need a real challenge.
- Length caps on every public and admin text field, whitelisted image keys, 3 MB upload cap, and
secureHeadersfor the usual response headers. - An append-only audit log of every admin action.
All of it is covered by 101 integration tests running against the real Worker in the Cloudflare Vitest pool — the same code path production runs, local D1 and R2 included. This is the same checklist I run through on every client build — see how I approach security-minded development if you're evaluating a site of your own. If you want a look at how I handle a bug like this after launch rather than before it, Remediating a Legacy SQL Injection, Safely covers that side.
What I'd improve
- The frontend has no test coverage to speak of; the 101 tests are all API-side.
- Photos are served straight through the Worker from R2 with no resizing pipeline — fine at this scale, wasteful beyond it.
- The admin console's client state grew organically and could use a proper store now that it spans eight views.