close

DEV Community

Cover image for I shipped a SaaS in 5 days at 19 with Next.js 16, Supabase, and Paddle - here's the whole stack
Kushagra kartikey suman
Kushagra kartikey suman

Posted on • Edited on

I shipped a SaaS in 5 days at 19 with Next.js 16, Supabase, and Paddle - here's the whole stack

πŸŽ₯ Prefer watching? 90-second product demo on YouTube

I'm a 19-year-old solo developer who just shipped my first SaaS in 5 days. It's live at https://bountydesk.vercel.app and open source on https://github.com/kushagra1607/bountydesk

This is a no-BS technical breakdown of the stack, every architectural decision, what worked, and what I'd do differently.

The product (1-minute context)

BountyDesk is a submission tracker + Markdown report builder for bug bounty hunters. Free tier; $7/mo Pro. The stack is what this post is about.

The full stack

  • Frontend: Next.js 16 (App Router, Server Actions, Node.js runtime)
  • Styling: Tailwind v4
  • Auth + DB: Supabase (Postgres + RLS + Auth)
  • Billing: Paddle (Merchant of Record - handles tax in 100+ countries)
  • Analytics: PostHog (self-host or cloud, both work)
  • Hosting: Vercel

Monthly cost to run: ~$0. All free tiers.

Architecture decisions

1. Supabase RLS for everything

Every table (submissions, programs, profiles) has Row-Level Security enabled. The policy shape:

create policy "own rows" on public.submissions
  for all using (auth.uid() = user_id)
       with check (auth.uid() = user_id);
Enter fullscreen mode Exit fullscreen mode

A user cannot read or write another user's data, ever, at the database level. Not just app-layer enforcement - Postgres itself blocks it.

Gotcha I hit: my initial profiles table allowed user UPDATE without column restriction. Any user could run UPDATE profiles SET plan='pro' WHERE id=auth.uid() from their browser console - instant free Pro. Caught it in pre-launch audit. Fixed by revoking UPDATE entirely from the authenticated role; only the Paddle webhook (service_role) can write.

2. Paddle as Merchant of Record

I'm in India. Selling globally means VAT in 27 EU countries, GST in Australia, etc. Paddle is a Merchant of Record - they take legal/tax responsibility. I just receive a payout in INR.

Fee: 5% + $0.50 per transaction. On a $7/mo subscription, I see ~$6.15 in my Paddle balance, ~$6.00 after Payoneer FX β†’ INR.

3. Webhook signature verification

The webhook handler verifies the Paddle HMAC signature on the raw body (before any parsing):

const signature = request.headers.get("paddle-signature") ?? "";
const body = await request.text();

try {
  const event = await getPaddle().webhooks.unmarshal(
    body, secret, signature
  );
} catch {
  return NextResponse.json({ error: "bad signature" }, { status: 400 });
}
Enter fullscreen mode Exit fullscreen mode

If you parse the body first (request.json()), Node mutates whitespace and signature check fails. Always read raw text first.

4. PostHog β†’ Slack for real-time alerts

PostHog has CDP (Customer Data Platform) functions that fire actions on event matches. I have three:

  • user_signed_up β†’ Slack: "New signup!"
  • upgrade_checkout_opened β†’ Slack: "Upgrade clicked!"
  • subscription_activated (fired server-side from the Paddle webhook) β†’ Slack: "PAYMENT RECEIVED!"

I literally know the moment a customer pays before Paddle even shows it in their dashboard.

5. AGPL-3.0 license

Open source with a viral copyleft. If someone forks the code, modifies it, and runs it as a competing SaaS, they must open-source their changes too. Same license PostHog, Plausible, and Cal.com use.

What I'd do differently

  1. Start the HN account age clock 30 days BEFORE launch. HN auto-flags new-account Show HN posts. Learned this the hard way.
  2. Build distribution while you build the product. I shipped the code in 5 days but spent 0 days on Twitter or community. By launch day I had no audience to broadcast to.
  3. Get a real domain. vercel.app subdomain looks indie-ish. ~$10/year for bountydesk.com would have signaled "real product."
  4. Open source from day 1, not day 30. Stars and contributions compound.

What I'd repeat

  1. Paddle. Selling globally on day 1 with no compliance burden is magical.
  2. Supabase RLS. Catches what app-layer code forgets.
  3. PostHog + Slack alerts. Real-time visibility into every conversion.
  4. Vercel free tier. Zero deploy friction.

The code

All ~70 source files on GitHub: https://github.com/kushagra1607/bountydesk

Interesting files:

  • src/app/api/paddle/webhook/route.ts - webhook + signature verification
  • src/lib/supabase/admin.ts - server-only service-role client
  • supabase/schema.sql - full schema with RLS policies
  • src/components/ReportBuilder.tsx - the report generator

Try it

Live: https://bountydesk.vercel.app

Sign up, log a fake submission, tell me what sucks. I respond to all DMs/comments.

β€” Kushagra (@bountydesk on X)

Top comments (0)