Vercel vs. Firebase: Two Route Handlers Instead of a Function per Endpoint
This storefront's whole backend is 2 Route Handlers, 8 Server Actions and a 42-line Edge proxy — no Cloud Functions, no separate BaaS deploy.
Vercel hosts a framework; Firebase is a backend-as-a-service with its own database, auth and functions runtime. Most comparisons resolve that mismatch by calling them "complementary" and stopping there. This storefront runs on Vercel and answers a sharper question: with Next.js Route Handlers and Server Actions doing the work, what would Firebase's Cloud Functions actually replace? Here, exactly two Route Handlers and eight Server Action modules — not a function per endpoint.
The short answer
Vercel is a hosting and compute platform built around a framework; it ships no database, no auth SDK, and no functions runtime of its own — you bring those. Firebase is the opposite shape: a bundled backend (Firestore, Auth, Cloud Functions) that expects you to write against its SDKs. Pick Vercel when a framework's own primitives — Route Handlers, Server Actions, Edge middleware — already cover your backend surface. Pick Firebase when you want a database, auth and callable functions provisioned together with no schema to design.
What each platform actually is
| Vercel (this storefront) | Firebase | |
|---|---|---|
| What it hosts | A Next.js app: pages, Route Handlers, Server Actions, an Edge proxy | A static frontend (Firebase Hosting) plus a backend (Firestore/Realtime DB, Auth, Storage) |
| Backend surface | 2 Route Handlers, 8 Server Action modules, all in one deployment | Each Cloud Function is typically its own deployment unit, invoked by HTTPS, a Firestore trigger, or Pub/Sub |
| Database | Not included — this app brings Postgres via Supabase | Firestore or Realtime Database, bundled |
| Auth | Not included — this app brings Supabase Auth, refreshed by a 42-line Edge proxy on every request | Firebase Authentication, SDK-managed, its own emulator and console |
| Background work | after(), backed by Vercel's waitUntil(), keeps a fire-and-forget promise alive past the response | Cloud Functions triggers, or Cloud Tasks for work deferred past the request |
| Env vars | Project-level; rebuild vs. restart semantics decide whether a change is live (see Next.js deployment) | Set per-function; Gen 2 functions read .env files bundled at deploy time |
| Pricing shape | Usage-based compute, Pro from $20/mo | Freemium BaaS — a generous free tier, then pay per read/write/invocation |
The pricing row is the one most comparisons stop at, and it is the least useful without a build to measure against — Firebase Hosting cost against this repository's actual 156.5 MB output runs that arithmetic and finds storage is free at this scale while transfer is the line that bills.
The row worth sitting with is backend surface. Firebase's unit of backend logic is the Cloud Function: one deployable, one cold start, one set of triggers. This app's backend logic is two Route Handlers and eight Server Action files, and none of them is a separate deployment — they ship inside the same Next.js build that renders the pages.
What this storefront's backend actually is
Counted in this checkout, not estimated:
src/app/api/download/route.ts
src/app/api/webhooks/lemonsqueezy/route.ts
src/lib/actions/account.ts
src/lib/actions/auth.ts
src/lib/actions/checkout.ts
src/lib/actions/contact.ts
src/lib/actions/entitlementsView.ts
src/lib/actions/newsletter.ts
src/lib/actions/redemption.ts
src/lib/actions/refund.ts
Two Route Handlers, eight Server Action modules, and one 42-line Edge proxy (src/proxy.ts) that runs ahead of nearly every request to refresh the auth session. On Firebase, the rough equivalent — a webhook receiver, a gated download endpoint, and eight callable mutations — would be ten separate Cloud Functions, each with its own cold-start profile and its own entry in the Firebase console. Here it is ten files in one Next.js app, deployed as one unit.
That is not automatically a win — a monolith you cannot scale a single hot path independently is a real tradeoff — but it is the concrete shape of "Vercel doesn't need a BaaS to have a backend," rather than the abstract claim.
The defect Firebase's model would not have caused this way
The one Route Handler with the least forgiving failure mode is the LemonSqueezy webhook — it sends a purchase-confirmation email after the entitlement transaction has already committed, so the email can never be allowed to affect the response:
// src/app/api/webhooks/lemonsqueezy/route.ts
export async function POST(req: Request) {
const raw = await req.text();
// ...verify signature, run processWebhook...
return new Response(result.body, { status: result.status });
}
The email send is wired in as a callback:
// Scheduled via `after()` (next/server) rather than invoked directly:
// on Vercel a fire-and-forget promise that isn't awaited has no
// guaranteed lifetime once the response is sent — the invocation can
// freeze/terminate before the Resend call completes, silently dropping
// the email. `after()` is backed by Vercel's `waitUntil()`, which keeps
// the invocation alive for scheduled work without blocking the
// response itself (verified against next/dist/docs/.../after.md).
sendPurchaseWelcome: (email, tier) =>
after(() => sendPurchaseWelcome(email, tier)),
That comment is describing a real, Vercel-specific gotcha: a serverless invocation on Vercel does not keep running just because you started a promise and didn't await it. The instant the Response goes out, the function can be frozen — and an un-awaited fetch() to Resend can lose the race and simply never complete. after() exists precisely to fix this, by handing the callback to Vercel's waitUntil(), which keeps the invocation alive for scheduled work without holding up the response.
Firebase Cloud Functions have a related but different version of the same constraint: a Gen 2 HTTPS function (backed by Cloud Run) also finishes its billable execution when the response is sent, and background work started inside the handler is not guaranteed to complete either — the documented fix there is Cloud Tasks or a separate triggered function, not a same-request callback. The failure mode is the same shape — "the platform doesn't owe your fire-and-forget code any time after you respond" — but the fix is platform-specific in both directions. Neither is more correct; treating either platform's request lifecycle as "just Node" is what breaks.
Auth and the site-wide risk that comes with bringing your own
Because Vercel doesn't bundle auth, this app owns it — Supabase Auth, refreshed on every request by src/proxy.ts. That file runs on a matcher covering almost the whole site, including pages with no login UI at all, and both of its Supabase env vars carry a non-null assertion. Ship this app to a Vercel project that hasn't set them, and the proxy throws on every request, marketing home page included — a failure mode CLAUDE.md warns about in bold. Firebase sidesteps this specific trap because its SDKs fail per-call rather than in a request-wide middleware layer, but it trades that for its own version: an unconfigured Firebase project fails individual getAuth()/getFirestore() calls at the point of use, which is easier to isolate but easier to miss in testing because unrelated pages keep working. The full accounting of this tradeoff — RLS policies, the service-role split, the bundle cost of the auth SDK — is in Vercel Postgres vs. Supabase; this post only needed the one failure mode that's specific to bringing an external auth provider onto Vercel's request lifecycle at all.
Mistakes and troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| A fire-and-forget email or log never arrives, only on Vercel | The invocation froze once the response was sent; an un-awaited promise has no guaranteed lifetime | Wrap it in after(), which is backed by waitUntil() |
| Every route 500s after deploying to a fresh Vercel project | An Edge proxy/middleware reads an env var with a non-null assertion, and the project's env vars aren't set yet | Set the vars before the first production deploy, not after the first 500 |
| A Firebase Cloud Function is slow on the first request after a while | Cold start — a new container has to boot for that function | Bundle related logic into fewer functions, or configure minimum instances |
| "Where do I put a background job" on Vercel | There's no built-in equivalent to a Firestore-triggered function | Use Vercel Cron Jobs for scheduled work, or a Route Handler triggered by an external webhook/queue |
| Confusing the Edge proxy with a Route Handler | Both run server-side, but the proxy is matcher-scoped and meant for cross-cutting concerns, not endpoint logic | Keep actual endpoints in Route Handlers; use the proxy only for things every matched request needs |
Frequently asked questions
Can I deploy Firebase Cloud Functions to Vercel? No. Cloud Functions are Google Cloud-specific — they run on Google's infrastructure and are triggered through Firebase's own event system. Porting one to Vercel means rewriting it as a Route Handler or Server Action; the code that touches Firestore or Firebase Auth goes away entirely if you also drop those services.
Do I still need Firebase if I'm hosting on Vercel? Not necessarily. Vercel's Route Handlers and Server Actions cover a real amount of what a BaaS's functions layer does, as this app's two Route Handlers and eight Server Actions show. What Vercel does not give you is a database or an identity provider — you still need one of those from somewhere, whether that's Firebase, Supabase, or a database you provision separately.
Is Vercel a Backend-as-a-Service like Firebase? No. Vercel is a hosting and compute platform for frontend frameworks. It has no bundled database, no auth SDK, and no document store. Everything in that category — Postgres, Supabase, Firebase, or otherwise — is something you add.
Can I use Firebase's database with a Vercel-hosted app? Yes, and it's a common pairing: Firebase supplies Firestore/Auth, Vercel hosts and renders the Next.js app that calls them. It's the same "hosting platform plus a chosen backend" shape this storefront uses with Supabase instead.
Templates in this post
ASoc Sentinel is a security-suite landing page leading with a live protection-status hero — threats blocked, devices covered, VPN status — plus a monthly/yearly pricing table. ASoc Signal markets an AI voice-and-image platform: voice cloning, script-to-voiceover generation, and a six-tool image studio behind a dashboard mock. ASoc Sterling is a wealth-management marketing site pairing a total-balance dashboard preview with advisor-reviewed portfolio management and tax-smart investing messaging.
Browse the full sets: Next.js landing page templates, Tailwind landing page templates.
