Engineering · April 30, 2026 · 5 min read

The cron piggyback: how we got 4 leaderboards out of 1 Vercel function

The Vercel Hobby plan caps you at 12 serverless functions and 1 cron job. We needed history features for a producer leaderboard, a mover board, a beat-of-the-week, and (counting) — without burning a single new function slot.

The problem

Every history feature on StudioMode has the same shape: once a day, freeze the current state of some live aggregate and write it to an append-only table. Today's leaderboard becomes yesterday's leaderboard, which becomes the data behind a "trends over time" feature months later.

The naïve plan looks like this:

Three problems. Vercel Hobby allows exactly one cron. Each cron is a function (so we'd burn 3-4 of our 12 slots). And the wall clock between them isn't synchronized — if cron 1 runs at 14:00 and cron 2 at 14:05, the snapshots disagree about what "today" is across timezones.

The piggyback

We already have a daily-digest cron — it runs once at 14:00 UTC and pushes notifications to users about new beats from producers they follow. That function is the only cron we use. Snapshot RPCs are short, idempotent, and run inline at the top of that handler:

// /api/notifications/daily-digest.ts
export default async function handler(req, res) {
  // 0a. Snapshot today's producer leaderboard.
  await supabaseAdmin.rpc('snapshot_producer_rank_history');

  // 0b. Same idempotent piggyback for beat-of-the-week.
  await supabaseAdmin.rpc('snapshot_beat_of_the_week');

  // 1. The actual digest work…
}

Two snapshot calls. Maybe 50ms total. The digest does its real job afterwards. Same cron run, same wall clock, same function slot.

12
function cap
1
cron used
4
snapshots

The idempotence that makes it work

The thing that makes this safe is that the snapshot RPCs are idempotent on (snapshot_date). Today's row gets inserted once and any second call same-day is a no-op:

insert into beat_of_the_week_history (snapshot_date, ...)
select current_date, ...
on conflict (snapshot_date) do nothing;

That tiny clause is doing serious work. It means:

Idempotence turns piggybacking from a hack into a contract.

Why not just use a database cron?

Supabase has pg_cron, which would actually work here. We considered it. Two reasons we didn't:

  1. Observability. Vercel's cron logs surface in the same dashboard as our other functions. pg_cron runs invisibly inside the database; failures don't page anyone and there's no obvious "did it run today?" panel.
  2. Simplicity over correctness margin. The digest cron is already running, already monitored, already the source of truth for "we did our daily work." Hooking snapshots into it gives us one thing to watch instead of two.

The pattern, generalized

If you're on Vercel Hobby (or any platform with a function quota), look for these:

Tick all four boxes and you can keep adding history features forever inside one function. We're at four; the next one probably won't need its own cron either.

What I'd do differently

Add a tiny health-check column to each snapshot table — created_at timestamptz default now(). Lets us write a one-line "did we snapshot today?" dashboard query without relying on Vercel's log retention. It's the smallest amount of future-proofing that I keep wishing I'd done from day one.

Find beats. Build a vault.

StudioMode is a discovery layer that surfaces beats artists actually use. Free for now.

Try StudioMode →
© 2026 StudioMode · Home · Blog