Engineering · April 30, 2026 · 8 min read

How we shipped a content site on 12 Vercel functions

The Vercel Hobby plan caps you at 12 serverless functions. StudioMode ships every share card, every server-rendered beat page, two cron-driven email jobs, a Stripe webhook, a YouTube scraper feed, and an AI search endpoint inside that limit. Here's how the budget actually breaks down — and the tricks we used when the cap got tight.

Why the limit matters

Hobby is free. Pro starts at $20/user/month, and the next-up tier (where the function cap effectively goes away) is $20/user. For an indie product still pre-revenue, that's a real budget line. We decided early to stay on Hobby until we had paying users — everything we built had to fit inside 12 function files.

Vercel counts the cap by file, not by route. So api/foo/[id].ts is one function regardless of how many query strings you support. The trick to the whole game is packing routes into one file via query params instead of spinning up a new file per endpoint.

Vercel counts the cap by file, not by route. So one file with smart query dispatch is one function — even if it serves 10 endpoints.

The full budget

12 / 12 used

01
api/beat/[id].ts — SSR for /beat/:id with proper og:image meta tags. Crawlers don't run JS.
02
api/p/[name].ts — SSR for /p/:producer producer profile pages.
03
api/r/[slug].ts — Referral redirect handler.
04
api/og.ts — THE big one. 10+ card types via ?type=. See breakdown below.
05
api/notifications/daily-digest.ts — Push notifications cron.
06
api/notifications/daily-digest-email.ts — Email digest cron.
07
api/notifications/drop-alerts.ts — Followed-producer drop pings.
08
api/ai/search.ts — "Find My Sound" natural-language search.
09
api/stripe/checkout.ts — Stripe Checkout session creation.
10
api/stripe/webhook.ts — Subscription state sync from Stripe events.
11
api/revenuecat-webhook.ts — iOS subscription state from RevenueCat.
12
api/waitlist.ts — Pre-launch + /launch email capture.

The big trick: one function, many endpoints

api/og.ts is the workhorse. It's one file that handles:

All dispatched by ?type=… in the query string. Each handler is a pure function returning a string (SVG for cards, XML for sitemaps); the top-level handler picks the right one based on req.query.type and sets the correct Content-Type. Total source: ~600 lines, mostly templates.

We could have used @vercel/og for richer images (gradients, custom fonts) but that ships a chunk of code that pushes cold-start times above 1s and would have meant a separate function file. Hand-rolled SVG is faster and keeps everything in one slot.

Vercel rewrites are free routing

The other multiplier: vercel.json rewrites cost zero functions. Every clean URL in StudioMode — /wrapped, /tarot, /compat, /embed/:id, /blog/:slug — is just a static HTML file routed via a rewrite. The HTML hydrates client-side from Supabase REST.

So the real surface area of the site is way bigger than the function count suggests:

All of that is one Hobby project. $0/month to host.

The trick we didn't use (yet)

You can stretch further by collapsing more endpoints into the mega-function pattern. For instance, all the notification cron jobs could be one file dispatched by a ?job= param, and the Stripe + RevenueCat webhook handlers could share a file. We didn't do that because each of those needs different secrets + logging boundaries — collapsing them risks a webhook-replay bug bringing down the cron, etc. Function isolation is the safety property; we'd only collapse if we hit the wall.

We're at exactly 12. We have some wiggle room. If we need a 13th endpoint we'd merge the cron jobs first.

What we'd skip if doing it again

Don't reach for serverless until you need it. For a content-heavy site with auth + a database, static HTML + client-side fetch from Supabase REST handles 90% of the use cases. We only spend function slots on:

  1. Things crawlers must see (SSR for og:image and meta tags).
  2. Things the database can't safely do via RLS (Stripe webhooks, AI search with rate-limited keys).
  3. Things that need a deadline (cron jobs).

Everything else is HTML + a Supabase REST call from the browser. Cheaper, faster, more debuggable. The function cap forced us into this discipline. Without it we'd probably have ten more endpoints that didn't need to exist.

Try StudioMode

Built on $0/month infrastructure. 14-day Pro trial.

🎧 Open StudioMode
© 2026 StudioMode