Building the YWCC Capstone and RWC Web Platform
How we built three independent program websites from a single shared codebase using Astro, Sanity, and Cloudflare — at just $15/month combined operating cost.
If you strip away the CMS and the frontend framework, everything in the YWCC Capstone and RWC platform runs on Cloudflare. Not because we wanted to be locked in, but because the free and near-free tiers align perfectly with a low-budget academic project that needs to scale to real traffic.
This article is a tour of every Cloudflare service we use, what role each one plays, and the specific constraints that led us to pick it.
Every public page on all three sites is statically generated by Astro at build time and served by Cloudflare Pages. Pages gives us:
main rebuilds and deploys automaticallyEach of the three program sites (Capstone, RWC US, RWC International) has its own independent Cloudflare account with its own Pages project pointing at the shared GitHub repository. Environment variables parameterize the build for each site.
The public pages are static, but the Sponsor Portal needs authentication, database access, and real-time data. Astro's hybrid output lets us opt specific routes into server-side rendering by adding export const prerender = false, and those routes run as Cloudflare Workers.
Cloudflare Workers Paid ($5/month) gives us:
The $5/month is the only recurring cost per site. Three sites = $15/month total for all infrastructure.
The Sponsor Portal uses Better Auth for Google OAuth, GitHub OAuth, and Magic Link sign-in. Better Auth needs a database for session storage and user records, and Cloudflare D1 (edge SQLite) is the natural fit:
Because D1 is SQLite, the schema is versioned in migration files in the repo. Running migrations during deploy keeps every environment in sync without any DBA work.
For data that doesn't need SQL's relational guarantees — session tokens, rate limit counters, cached API responses — we use Cloudflare KV. KV is eventually-consistent key-value storage with 100,000 reads per day on the free tier, which is plenty for a portal that serves a few hundred sponsors.
Sliding-window rate limiting is surprisingly hard without persistent per-client state. We use a Cloudflare Durable Object (a single-instance stateful Worker) as a dedicated rate-limiter deployed as a separate worker (rate-limiter-worker/). The portal middleware forwards the client IP to this worker and gets back an allow/deny response.
The rate-limiter enforces 100 requests per 60 seconds per IP on portal routes. Because Durable Objects guarantee single-instance state, we get consistent counts without race conditions even under bursty traffic.
The AI chatbot visible on public pages is powered by Cloudflare AI Search (previously called NLWeb Worker) and the @cloudflare/ai-search-snippet web component. Here's what that means in practice:
@cloudflare/ai-search-snippet) that renders in a shadow DOM and hits the NLWeb endpoint directlysiteSettings.aiSearch toggle in Sanity Studio — we can enable or disable the chatbot per site without a code deployThis replaces what would otherwise be a custom pipeline with OpenAI embeddings, Pinecone, and a custom chat API. Instead we get AI-grounded answers about our program with literally zero code to maintain.
The sponsor inquiry form and contact form are protected by Cloudflare Turnstile, an invisible CAPTCHA that replaces reCAPTCHA. Turnstile:
Form submissions also trigger a Discord webhook notification so the team is alerted to new sponsor inquiries in real time.
The Aggregated Platform API (delivered by Team B as part of Scope Document 2) uses Cloudflare Queues as an asynchronous event bus. When a form is submitted, or a content change webhook fires, or a scheduled task runs, events are pushed onto a Queue and consumed by downstream workers. This keeps the request/response path fast while still supporting complex async workflows.
| Service | Cost | Notes |
|---|---|---|
| Cloudflare Pages (3 projects) | $0 | Free tier, unlimited bandwidth |
| Cloudflare Workers Paid (3 accounts) | $15/month | $5/month per site × 3 |
| Cloudflare D1 | $0 | Free tier (5M reads/day) |
| Cloudflare KV | $0 | Free tier (100k reads/day) |
| Cloudflare Durable Objects | $0 | Included in Workers Paid |
| Cloudflare AI Search + NLWeb | $0 | Included in Workers Paid |
| Cloudflare Turnstile | $0 | Free and unlimited |
| Cloudflare Queues | $0 | Included in Workers Paid |
| Total | $15/month | For 3 production sites |
We picked Cloudflare over AWS, Vercel, and Netlify for one overriding reason: the free tiers and unit economics match a student project budget. AWS free tier expires after 12 months. Vercel's free tier has bandwidth limits that make it unsafe for a public-facing site. Netlify's paid tier starts at $19/month per site.
Cloudflare's model — free static hosting + a fixed $5/month per Workers-enabled site — gives us predictable costs with real headroom. And the fact that everything (D1, KV, Workers AI, AI Search, Turnstile, Queues) lives behind the same wrangler CLI and the same bindings model means there's one mental model to learn instead of seven different AWS service SDKs.
In the next article, we'll go deeper into the multi-site architecture and how we use the same codebase to deploy to three independent Cloudflare accounts without cross-contamination.

Subscribe for new articles delivered to your inbox.
How we built three independent program websites from a single shared codebase using Astro, Sanity, and Cloudflare — at just $15/month combined operating cost.
We use cookies to analyze site traffic and optimize your experience. By clicking "Accept," you consent to our use of analytics cookies.