DEV Community

Cover image for How I built a SaaS in a weekend with Claude + Next.js — the honest breakdown

How I built a SaaS in a weekend with Claude + Next.js — the honest breakdown

Info Inlet on October 17, 2026

I built the first working version of my SaaS in a weekend with Claude and Next.js. That sentence is true, and it's also the least interesting thing...
Collapse
 
sgaggjhkjh profile image
Arjun Sharma •

That webhook story — acking the 200 before the write, fixed by two lines reordered — is the scariest-cleanest bug anecdote I've read in a while, and the author/skeptic/human split is one I'm stealing for my own reviews.

Collapse
 
infoinlet1 profile image
Info Inlet •

Steal away — that's what it's for 😄 The part that surprised me most: the skeptic pass adds almost no friction once it's a separate step. The hard bit was psychological — admitting the model that just impressed me shouldn't also be the one I trust to bless it. If you try it, that one question — "what happens on a retry?" — punches way above its weight. Would love to hear how it lands in your reviews.

Collapse
 
sgaggjhkjh profile image
Arjun Sharma •

The retry question goes on my review checklist today — I'll report back the first time it catches something the skeptic pass missed.

Thread Thread
 
infoinlet1 profile image
Info Inlet •

Love it — a checklist line is exactly the right home for it, because it survives the days you're tired and inclined to trust the clean diff. That's the whole point of making it structural instead of "be careful."

One small tip from running it a while: when it does catch something, write down the one-line question that caught it, not just the bug. The checklist grows by questions — "what happens on a retry?", "what happens when this is called twice?", "what happens when the input is empty?" — and each one you add is a class of bug you never have to rely on scar tissue for again.

Looking forward to the report-back. 🙏

Thread Thread
 
sgaggjhkjh profile image
Arjun Sharma •

Capturing the question rather than the bug is a great framing. It grows the checklist into a set of invariants instead of an incident log, which is exactly what keeps it useful when the team changes.

Collapse
 
mudassirworks profile image
Mudassir Khan •

the ack before persist ordering is the one that bites everybody once. we got hit by the flip side right after fixing it: the provider retried during a slow write and we processed the same payment event twice, so a unique constraint on the event id ended up doing more work than the handler logic.

and yeah the model cannot see that class of bug because the diff reads fine, it only shows up in a failure trace.

did your skeptic pass get the failure scenarios as input, or just the diff?

Collapse
 
infoinlet1 profile image
Info Inlet •

You and Julian landed on the exact same spot from different scars — the unique constraint on the event ID quietly doing more than the handler logic. That's two independent "it bit us once" votes in one thread, which is about as strong a signal as this stuff gets. The lost write and the double-process are the two faces of the same retry, and only one of them is fixed by reordering.

To your direct question — honestly, that weekend the skeptic got just the diff. There were no failure scenarios to feed it, because the whole finish line lived in my head; that's precisely why catching it still depended on my own scar tissue rather than the system. Which is the weakness a couple of other folks in this thread pushed on too.

Where I've moved since: the skeptic works far better with the failure scenarios as explicit input — but written before the build, as the acceptance condition for the stage ("a failed write leaves the provider retrying and every customer ends with exactly one record"). Then the skeptic isn't free-associating about what might go wrong; it's checking the diff against a claim that must hold, and "what about the retry that fires twice?" falls straight out of "exactly one record." Diff-only is the weak version. Diff-plus-a-written-invariant is the one that would've caught this without me having been burned first.

And your last point is the one I'd underline for everybody: it only shows up in a failure trace, never in the diff — which is exactly why the author can't be the one to bless it. Great question. 🙏

Collapse
 
parsaaftabi profile image
Parsa Aftabi •

the unique constraint ending up as the real handler is the part that gets me. once the write is slow enough to trigger a retry, that one insert is the entire gate.

Collapse
 
infoinlet1 profile image
Info Inlet •

Right — and the part that makes it elegant is that the constraint is the only gate that holds no matter how the handler misbehaves. The handler can reorder, retry, run twice concurrently, get refactored by someone who's never heard of this thread — and the second insert still slams into the constraint and fails. Every other guardrail is a promise the code has to keep on every path; the unique index is a promise the database keeps for you, once.

The bit worth saying out loud: under a slow write, those retries can land concurrently, so two inserts race for the same event ID at the same instant. That's exactly the case handler-level "check then write" logic loses — both reads see nothing, both proceed. The constraint is the one thing that still holds under the race, because the DB serializes it for you. One winner, one conflict you catch and no-op.

So yeah — once you're in retry territory, that single insert is the handler. The rest is bookkeeping around it. Good eye. 🙏

Thread Thread
 
parsaaftabi profile image
Parsa Aftabi •

check-then-write is where I see this break most in reviews. two reads come back empty, both proceed, and the code looks correct the whole time.

Collapse
 
contentclips_st profile image
ContentClips •

The honest breakdown framing is refreshing. My 2 cents from shipping client deliverables on tight turnarounds: the bottleneck in a weekend build is almost never the code - it is the transition moments: environment setup, deployment config, docs. Claude handles code generation brilliantly, but I have learned to budget the remaining 20% of the time for the plumbing that makes a thing actually shippable: CI, a real landing page, searchable docs. Buyers judge "shipped" by what they can click, not by what runs on localhost. Nice writeup.

Collapse
 
infoinlet1 profile image
Info Inlet •

"Buyers judge 'shipped' by what they can click, not by what runs on localhost" — that's a keeper, and it's the honest counterweight to my whole post. I spent 2,000 words on correctness because that's the bug that scared me, but you're pointing at the thing that actually kills most weekend projects: they never cross the gap from "runs on my machine" to "a stranger can touch it."

And I think the two points are the same point wearing different clothes. The plumbing — CI, deploy config, a real landing page, searchable docs — is exactly the stuff that's unglamorous, has no clean diff to admire, and so is the first thing the "I built a SaaS in a weekend" genre quietly skips. The ack-before-persist bug and the missing CI pipeline are both the hard part hiding behind the part that got easy. Code generation got cheap; everything around the code did not.

The one thing I'd add to your 20% budget from the correctness side: the plumbing is also where you install the skeptic cheaply. CI is the natural home for "run the adversarial check on every diff" — so the same pass that makes it clickable can be the pass that makes it right. Fold them together and the 20% buys you both.

Appreciate the field notes from actual client turnarounds — that's the view the weekend-flex posts never show. 🙏

Collapse
 
contentclips_st profile image
ContentClips •

Folding them together is the right instinct, with one guard: the adversarial pass and the deploy check need to run against the same artifact, otherwise you rebuild the classic 'green CI, broken deploy' split one level up — build the bundle once and feed both passes from that single artifact. Second point: make the skeptic emit a report artifact instead of a bare pass/fail. A pass/fail answer goes stale in a week and someone eventually deletes the job for slowing the pipeline; a report that shows what it challenged and why is what survives the budget review. And budget a slice of the 20% for the skeptic's own feedback loop — every false positive is a direct tax on the pipeline's credibility, and unpriced credibility taxes are how these checks get quietly turned off.

Collapse
 
ascendra_ventures_92c21f2 profile image
Ascendra Ventures •

The ack-before-persist handler is a perfect example, because nothing about it looks wrong. It compiles, it has a thoughtful comment, it passes the test the same model wrote, and it still loses a paying customer on the one bad night.

"The thing that writes the code cannot be the thing that blesses the code" is the line I would keep. The author, skeptic and human split works for client work too, with one addition: the skeptic needs something fixed to test against. If the finish line for the payment stage is written before the build, for example a failed write leaves the provider retrying and every paying customer has a record, the skeptic pass becomes a check against that list rather than an open hunt.

Your list of what a weekend does not buy, the empty states, the billing edge cases, the second user, is what founders need to read before asking how long the rest will take. How do you prompt the skeptic so it does not simply agree with the author?

Collapse
 
infoinlet1 profile image
Info Inlet •

You've put your finger on the thing that makes the skeptic actually work versus theater, so let me take both parts.

The spec point is exactly right, and it's the upgrade I'd make to the post if I rewrote it. An open-ended "find the bug" skeptic is weaker than one handed a finish line written before the build — "a failed write leaves the provider retrying and every paying customer ends with a record." Now the skeptic isn't hunting in the dark; it's checking the diff against a claim that must hold. Your framing — write the acceptance condition for the payment stage first, then the skeptic verifies against it — is better than what I did that weekend, where the finish line lived only in my head. Written-down beats remembered, every time.

On your direct question — how to stop the skeptic just agreeing — three things that moved the needle for me:

  1. Change the job, not the tone. Don't ask "is this correct?"k "here is a diff that is assumed to have a bug that losesmoney; produce the input sequence that triggers it." The presupposition does the work. It's no longer allowed to conclude "looks good"; it has to produce a failing case or explicitly argue why none exists.
  2. Separate the context. The skeptic shouldn't see the author's reasoning or reassuring comments — those are precisely what lull you. Give it the diff and the spec, nothing else. A fresh pass with no investment ig to call it broken.
  3. Make it adversarial by role, not by adjective. "Be critical" gets you a cosmetic nitpick. Framing it as a named failure mode — "you are the retry that fires 3 seconds after the ack; walk the handler" — gets versary beats abstract skepticism.

The honest limit: even prompted perfectly it won't catch evern stays on the merge button. But the gap between "is thisgood?" and "find the input that makes it lose money" is most of the value, and it's free.

And yes — that "what a weekend doesn't buy" list is the one I wish founders read before the "how long for the rest?" question. The rest is the product. Thanks for the sharp comment. 🙏

Collapse
 
julianneagu profile image
Julian Neagu •

The duplicate payment angle matters just as much as the lost payment. I’d make the event ID unique at the DB level too. Better to let the database enforce it than trust the handler to get every retry right.

Collapse
 
infoinlet1 profile image
Info Inlet •

This is the correct instinct and it's the deeper fix — thank you for pushing it one level down. My post stopped at "persist before you ack," but that only solves the lost write. You're pointing at the other half: once the provider retries, you process the same event twice, and now you've granted the entitlement twice or double-counted the payment. Reordering two lines does nothing for that.

And your framing of where to enforce it is the real lesson. A unique constraint on the event ID makes idempotency a property of the schema, not a promise the handler has to keep perfectly on every path. The handler can be sloppy, the retry can fire five times, a future refactor can reorder things again — and the second insert just fails on the constraint instead of silently duplicating. You catch the conflict and no-op. The invariant lives in the one place that can't be talked out of it.

Which, pleasingly, is the same lesson as the whole post from a different angle: don't trust the confident author — in this case the handler — to get every case right. Push the guarantee somewhere that physically cannot be charmed into agreeing. The DB constraint is the skeptic that never sleeps.

Great addition. 🙏