← Writing

Why I moved my agency's site off Firebase

Firebase Hosting is a fine product with a pricing model that stops making sense for static sites. The migration to Cloudflare took one working session — and left me with a deploy-safety rule I now apply everywhere.

On this page

The decision took longer than the migration.

My agency’s marketing site — a multi-page static site — had lived on Firebase Hosting since it was built. Firebase was the path of least resistance at the time: the team knew it, deploys were one command, and it worked. But the bill kept not matching the workload. A static marketing site is the cheapest possible thing to serve, and it was accruing cost anyway.

The conclusion I finally wrote down: we are dropping Firebase because it costs too much for what this site is. No performance complaint, no reliability complaint. Purely cost, purely fit.

The mismatch, in general terms

Firebase Hosting isn’t overpriced at what it’s for. It’s the front door to an ecosystem — Auth, Firestore, Functions, Storage — and its value assumes you’re inside that ecosystem. The hosting bill is easy to justify when it sits next to a Firestore-backed app using Auth and Cloud Functions.

A static marketing site uses none of that. It needs files served from a CDN, TLS, and a deploy pipeline. That’s the exact product Cloudflare Pages gives away: free builds from git, unmetered bandwidth, certificates handled at the edge. My whole $0 infrastructure stack is built on that asymmetry, and the agency site was the last holdout.

So the decision framework I’d offer anyone auditing their own hosting:

  1. Is the site static or mostly static? If yes, hosting is a commodity — pay commodity prices, which today are zero.
  2. Are you using the ecosystem the hosting is priced for? Firebase Hosting attached to a real Firebase app can be worth it. Firebase Hosting attached to nothing is a subscription to a lobby.
  3. What does leaving cost? For a static site: an afternoon. That asymmetry — recurring cost against one-time migration effort — almost always resolves in favor of moving.

The migration itself

Honestly, there isn’t much drama to report, which is the point. The site moved to Cloudflare in a single working session: build output onto Cloudflare’s hosting, DNS pointed over, redirects verified.

One detail worth copying: the deploy uses a token-scoped Cloudflare API token — scoped to exactly the permissions this one site’s deploy needs, nothing more. It’s a small habit with a good failure mode: if the token ever leaks from a CI log or a laptop, the blast radius is one static site, not an account.

The scar tissue: scope your deploys

The migration also gave me a chance to encode a rule I’d learned the uncomfortable way on Firebase, and it’s the most transferable thing in this post.

Firebase projects can host multiple sites. And firebase deploy --only hosting — with no site specified — deploys to all of them at once. If a project hosts a marketing site, a web app, and a landing page, one unscoped deploy of “just the marketing site” ships whatever’s in your working directory to all three. That is a routine command with a destructive default, and it’s shockingly easy to run on autopilot.

My rule since: never run an unscoped hosting deploy. Every deploy names its target explicitly — firebase deploy --only hosting:site-id — and every deploy script in package.json, Makefile, or CI bakes the target in so nobody has to remember. If the target can’t be determined unambiguously, the deploy doesn’t happen until it can.

Cloudflare’s model sidesteps this particular trap — each Pages project deploys itself — but the general rule survives the migration: any deploy command whose default scope is “everything” should be wrapped so it can only ever be run scoped. Deploy tooling should make the narrow action easy and the broad action deliberate.

What this adds up to

None of this is a takedown of Firebase — I still deploy client projects on it where the ecosystem earns its keep. It’s a takedown of defaults. The site was on Firebase because it had always been on Firebase; the bill was paid because it had always been paid. The audit took an evening, the migration took a session, and the recurring cost went to zero.

Most infrastructure spend I see in client work is exactly this shape: not waste from bad decisions, but waste from decisions nobody has re-made in years. Pull the thread. The savings are usually one afternoon away.

← All writing Book a call →
Book a call → WhatsApp