0015 - Serve the Web Application from Cloudflare Pages
Status: Accepted Date: 2026-08-12 Author: Manuel Nucci
Context
ADR 0007 adopted GCP for the backend "while keeping the web application on Vercel", and the GCP migration plan carried that forward as a global invariant and an explicit exclusion. That decision is no longer available on the terms it assumed.
Vercel's Hobby plan does not support deployment from a private repository owned by a GitHub organization, which is what feelproclub/fpc is. Its Fair Use Guidelines also restrict Hobby to non-commercial personal use, defining commercial usage broadly enough to include a project whose code is written by a paid employee or consultant. Deploying through the Vercel CLI bypasses the repository check but not the terms, and enforcement is discretionary and retroactive — an unacceptable failure mode for the surface that receives every shared invitation link.
The compliant option is Vercel Pro at USD 20 per user per month. That alone exceeds the migration plan's ideal USD 20 monthly development-plus-production baseline, before Cloud SQL's approximately USD 9.70.
Three further facts shape the alternative:
- The web output is a static bundle.
apps/appbuilds toapps/app/distthrough Vite, with no server-side rendering, no incremental static regeneration, and no edge functions. What Vercel is distinctively good at is what this application does not use. - ADR 0014 reduced the web to the invitation and landing surface, with Android as the launch product. The web host serves static assets to people opening invite links, and little else.
- The domain's DNS was hosted at Vercel rather than at its registrar, so leaving Vercel required moving DNS regardless of where hosting went.
Decision
Serve the web application from Cloudflare Pages, and host the domain's DNS at Cloudflare.
- This supersedes only ADR 0007's web-hosting clause. Every other decision in ADR 0007 stands: GCP remains the backend platform, and the application continues to call Cloud Run's stable public
run.appendpoint directly. - Build with the repository's existing commands. Cloudflare Pages builds
pnpm --filter @feelproclub/shared build && pnpm --filter fpc-app buildfrom the repository root and publishesapps/app/dist, matching whatvercel.jsonspecified. - Serve the single-page application through a catch-all rewrite to
index.html. Without it a direct request for/invite/<code>returns 404, which would break the one flow the web surface exists to serve. - Keep
app.feelproclub.comas the production origin. Only the host behind it changes, so the API's allowed CORS origins, the WorkOS redirect allowlist for production, and every documented URL stay as they are. - Route domain mail through Cloudflare Email Routing, forwarding to an existing mailbox. This follows from hosting DNS at Cloudflare and from the domain having no mail service of its own; it receives and forwards but does not send.
- Accept the free plan's terms: no service-level agreement and community-only technical support. Cloudflare permits commercial use on free plans, which is the requirement Vercel's Hobby plan could not meet.
Consequences
Positive
- Web hosting costs nothing rather than USD 20 per month, keeping the migration inside its stated cost envelope instead of exceeding it on the frontend alone.
- The arrangement is contractually sound for a commercial product, rather than depending on a workaround that could be enforced against at any time.
- Static asset bandwidth is unmetered, so an invitation link that spreads widely costs nothing. Vercel Pro includes a fixed bandwidth allowance and bills overage, which inverts the incentive on the exact event the product wants.
- DNS, web hosting, and domain mail sit with one provider, removing the split where the registrar, the DNS host, and the deployment platform were three different relationships.
Negative
- The free plan carries no service-level agreement, and technical support is community-only. An outage is waited out rather than escalated.
- Cloudflare Pages has a smaller frontend ecosystem than Vercel, so integration guides and community answers are less abundant.
- Email Routing forwards but cannot send, so replying from an address on the domain requires a separate sending service.
- Moving a zone between Cloudflare accounts is difficult, which makes the account the zone is created in an early and consequential choice.
Mitigations
- The absent service-level agreement is proportionate to what is served: static assets for an invitation and landing surface, with Android as the product. An outage degrades invitation links rather than the application itself.
- Keeping the build commands and the published directory identical to the previous configuration means the platform is replaceable again later with a comparable amount of work.
- Preserving
app.feelproclub.comconfines the change to infrastructure, leaving the API contract, authentication redirects, and documentation untouched. - Create the Cloudflare zone in an account intended for the product from the start, and change account membership rather than moving zones when ownership consolidates.