How to ship SaaS fast: the stack is easy, the plumbing is the project

The framework picks itself. Webhook idempotency, RLS, refunds, EU consent: the plumbing is what eats your launch. The anti-plumbing SaaS stack.

June 9, 2026

Ask ten founders how to ship SaaS fast and you get ten stack arguments. Next vs Nuxt, Postgres vs a hosted DB, Stripe vs Paddle. Pick whatever you already know and the stack debate is over in an afternoon. The framework is not what slows you down.

What slows you down is the plumbing between the pieces. We know, because we built BoiledPlate by writing that plumbing and then keeping a running log of every edge case it took to make it trustworthy. This site runs on it. Here is the stack we landed on, and then the part nobody warns you about.

The stack, settled in one paragraph

Nuxt 4 with TypeScript in strict mode for the app. Supabase for Postgres, auth, and row-level security. Stripe for payments, with webhooks as the source of truth. Resend for transactional email. Nuxt UI v4 on Tailwind for the interface, and @nuxtjs/i18n so the product speaks more than one language from day one. None of these choices are clever. They are the boring, well-documented options, and that is the point: every one of them has a paved path and an agent can stay on it.

You can assemble that list in a day. Then you discover the list was the easy 10 percent.

The 90 percent: plumbing you only notice when it breaks

A checkout button that charges a card is a demo. A billing system you can trust at 3am is a project. The gap between them is a pile of edge cases that each look like a one-liner until you actually hit them. Here are the ones that shaped our own commit history.

Webhook idempotency. Stripe retries deliveries. If your handler is not idempotent, a retry double-records a purchase or double-sends a confirmation email. Our handler claims the event id in a stripe_events table before doing any work and releases it on failure, so a retried event is recognized and skipped, but a genuinely failed one still gets reprocessed. That single rule (writes are driven by signature-verified, idempotent webhooks and nothing else) is what makes the billing state believable. We wrote a whole post on why webhooks are the source of truth because it is the easiest thing to get subtly wrong.

Refunds, not just charges. Most tutorials stop at the happy path. A real product has to handle a chargeback. Our charge.refunded case revokes the site entitlement, and because we deliver the product as a private GitHub repo invite, it also removes the buyer from that repo and cancels any still-pending invitation. Full refunds only, partial refunds keep access, and the revocation re-runs safely on Stripe retries even if the refund timestamp was already stamped. That is one event type, and it is four behaviors that all have to agree.

RLS in the same migration, every time. It is tempting to ship a table now and add policies "later." Later is how data leaks. Our rule is that every table turns on row-level security with explicit policies in the same migration that creates it. No table ever exists in a state where the wrong user can read it.

The legal plumbing. Selling digital goods to EU buyers means the 14-day right of withdrawal disappears the moment you grant immediate access, but only if the buyer expressly waives it. We collect that waiver as a required consent in Stripe Checkout, with the acceptance message localized, because the terms page depends on it. That is not a feature anyone demos. It is the difference between a sale you can keep and a refund you have to give back.

Ordering. Inside a paid checkout the steps run in a fixed order: record the purchase, deliver the product, then send the email. Get the order wrong and a delivery retry double-sends the receipt. None of this is hard once you know it. Knowing it is what costs the weeks.

Why this is the real "ship fast" lever

Add those up. Webhook idempotency, refund revocation across two systems, RLS on every table, EU consent collection, delivery state machines, password reset, auth email over SMTP, a sitemap, JSON-LD that does not crash on hydration. Each is a few hours of reading docs and a few more hours of getting the edge case right. Together they are the reason a "weekend SaaS" takes two months.

So the honest answer to "how do I ship SaaS fast" is not a framework. It is: do not write the plumbing again. That is the entire thesis behind BoiledPlate. The stack above comes pre-wired, the webhook is already idempotent, the refund path already revokes access, the tables already have their policies, and the EU waiver is already in the checkout. You start from the part that is actually your product.

The setup is plumbing too

There is one more place the days go: wiring the accounts. Creating the Stripe product and price, registering the webhook endpoint with the right event types, linking the Supabase project, pushing migrations, configuring the Google provider and the Resend SMTP. We automated that. Point a coding agent at the repo and its first session interviews you (name, languages, theme, billing model), reshapes the codebase to your answers, and then provisions Stripe and Supabase by running the setup scripts instead of clicking through dashboards. The conventions are written down so the agent stays on-pattern while it does it.

The result is that the boring, error-prone, well-understood 90 percent is already done and verified, which is exactly the 90 percent that was standing between you and a launch. If you want the longer tour of how the three services fit together, we wrote that up in the Nuxt + Supabase + Stripe boilerplate post.

Ship fast by spending your time on the 10 percent that is yours. Let the plumbing be plumbing.

#saas #nuxt #supabase #stripe

Read more