โ† All posts

The stack we'd ship today: Next, Postgres, Stripe, Resend

The opinionated default that lets us hit day seven without spending half the sprint on infrastructure decisions. And the rare cases we deviate.

Four stack layers stacked vertically: Next.js, Postgres, Stripe, and Resend.

There is an entire genre of blog post that frames stack choice as a deeply personal journey, an evolving philosophy, a dialogue between the engineer and the ineffable. This is not that post.

We run a productized 7-day MVP sprint. If every sprint started with a stack debate, we'd ship ten projects a year instead of twenty. The stack is a decision we've already made on behalf of the founder, and most of the time it's the right one. When it isn't, we deviate. But the default is the default because it keeps working.

The default

Here it is, without the fanfare:

  • Framework: Next.js (App Router)
  • Hosting: Vercel
  • Database: Postgres, managed โ€” usually Neon or Supabase
  • ORM / query layer: Drizzle (preferred) or Prisma
  • Auth: Auth.js, or Clerk when the product needs social sign-in and org management on day one
  • Payments: Stripe (Checkout for simple, Billing for subscriptions, the full API only when we actually need it)
  • Transactional email: Resend
  • Background jobs / queues: Trigger.dev or Inngest
  • Error monitoring: Sentry
  • Analytics: PostHog or Plausible, depending on whether the founder will use product analytics
  • File storage: Cloudflare R2 or the built-in options on the hosting platform
  • Secrets: Platform-managed. Never in the repo. Never.

That's it. No service mesh. No Kubernetes. No Kafka. No separate auth service. No GraphQL gateway. No custom deployment pipeline. A founder can log into every one of those services and see what they own โ€” that matters, and it's part of the reason we don't pick anything more exotic.

Why Next.js

Because it removes decisions. It gives us a router, a rendering model, an API surface, streaming, static optimisation, and a deployment target. We don't need to litigate the client/server split on day one; the framework has opinions we agree with.

The App Router, at this point, is stable enough to be the default. Server components plus server actions let us skip an entire category of boilerplate that used to be mandatory in a two-week timeline. We don't hand-roll fetch wrappers or request contexts. We write the function and let the framework put it on the right side of the wire.

The things we don't do in Next: we don't chase the edge runtime unless a feature specifically needs it. We don't build middleware for things that should be inside a route handler. We don't use the framework as a reason to avoid writing a type.

Why Postgres

Because it covers roughly 99% of the data problems an MVP has, and because "we'll pick a purpose-built database for each workload" is a sentence that reliably turns a 7-day sprint into a six-week sprint.

Postgres does relational. It does JSON. It does full-text search well enough that you rarely need Elasticsearch on day one. It does geo if you need it. It does pub/sub if you squint. It has a mature ecosystem of managed hosts, so we never run a database ourselves for a founder.

We lean on the ORM for the boring queries and drop to raw SQL when a query actually needs the attention. Drizzle in particular reads like SQL, which means there's no translation cost when we do drop down.

Why Stripe

Because payments are the one thing that must be boring. Stripe is boring. It has the right abstractions, documentation you can actually read, and a test mode that behaves like production. Every founder's accountant knows what a Stripe export looks like.

The implementation rule we follow: use the highest-level primitive that works. If Checkout handles the flow, don't touch Elements. If Billing handles the subscription, don't write your own recurring-charge logic. Every level you drop down is more surface area to test and more tax webhook edge cases to handle.

Why Resend

Because transactional email is usually boring, occasionally critical, and reliably annoying. Resend has the smallest set of things to learn, a decent React template story for well-formatted emails, and SPF/DKIM setup that won't swallow half a day. Anything else we've used has either required more ceremony or wanted to sell us a feature we didn't need.

The things we deliberately don't do on day one

The defaults above are a list of things we include. The list of things we leave out is at least as important.

  • A separate frontend and backend repo. One repo. One deploy. You do not need a microservice. (More on this one.)
  • A CMS. Almost no MVP needs one. Content lives in the codebase until it clearly shouldn't.
  • A mobile app. If the MVP is a mobile app, we'll say so on the discovery call. Almost always, a responsive web app is the right first version.
  • An admin panel built from scratch. Postgres + a read-only dashboard + some SQL snippets will take you a long way. A custom admin is sprint-three work.
  • An observability pipeline. Sentry plus the hosting platform's default logs plus PostHog is enough. A real tracing stack is for when a real operational problem exists.

When we deviate

The default isn't a religion. We change it when the product actually needs something different.

When the founder already has a stack. If the founder is a Rails engineer and will own the code after sprint one, building them a Next.js app serves no one. We meet them where they are.

When the domain needs it. Heavy real-time collaboration pushes us toward something like Liveblocks or a dedicated realtime service. High-throughput ingestion pushes us toward a queue with stronger guarantees. AI-heavy products usually add a vector database and a worker fleet. Those are product-led decisions, not preference ones.

When compliance requires it. Healthcare and regulated fintech regularly rule out some of our defaults. We adjust; we don't pretend the defaults are compliant when they aren't.

Why this is worth having an opinion about

The reason we take the stack question off the table isn't that the alternatives are bad. It's that the debate is expensive, especially when the team debating it hasn't shipped together before. A 7-day timeline doesn't have room for two days of "what if we used Go for the backend and React Native for the frontend and DynamoDB for the core data with a Redis side-car and a custom auth service." It has room to build the thing.

An opinionated stack is a gift we give to the founder. They don't get a buffet; they get a meal that's been cooked before. They also get a codebase that looks like a lot of other codebases, so any engineer they hire after the sprint can pick it up.

The one rule under all of this

Whatever the stack โ€” default or deviated โ€” one rule applies: it has to be something the founder can run themselves in six months without us. No weird in-house abstractions. No service we're operating on their behalf. No tooling they couldn't hand to another engineer. A stack the founder doesn't own is a dependency on us, and that's not the relationship we want. We'd rather be brought back in sprint two because the founder liked the first one.