The Architecture Decisions That Will Haunt You (or Save You)
When we tell founders we can get their SaaS MVP to market in 90 days, the most common response is scepticism. The second most common is: "What corners are you cutting?" The answer is: none of the corners that matter. We're ruthlessly scope-focused about features, but we refuse to compromise on the architectural decisions that are expensive to change later.
This post is a technical breakdown of the decisions we've settled on after building 40+ SaaS products — and why we made them.
Decision 1: Multi-Tenancy Model
This is the single most important decision you'll make and the hardest to change later. There are three approaches:
Shared database, shared schema (row-level tenancy): All tenants share tables, differentiated by a tenant_id column. Simplest to build and cheapest to operate, but requires careful query discipline to avoid data leaks. Every query must include a tenant_id filter.
Shared database, separate schemas: Each tenant gets their own PostgreSQL schema within a shared database. Better isolation, but schema migrations become more complex as you add tenants.
Separate databases per tenant: Maximum isolation and easiest compliance story, but operationally complex at scale (1,000 tenants = 1,000 databases to manage).
For most SaaS products under $10M ARR, we recommend shared database, shared schema with row-level security enforced at the database level (using PostgreSQL RLS). It's the right balance of simplicity, cost, and safety. We add separate-database options later for enterprise customers who require it as a contractual matter.
Decision 2: Authentication Strategy
Don't build auth. It's one of the clearest areas where the build vs buy answer is unambiguous. Auth is a security-critical surface area where mistakes can be catastrophic. Use Auth.js (formerly NextAuth) for self-hosted flexibility, or Clerk for an out-of-the-box solution with a generous free tier.
What you need from day one: email/password login, social OAuth (Google, GitHub at minimum), magic link login, session management, and a JWT strategy for API access. If you need SSO/SAML for enterprise customers, plan for it architecturally from the start even if you don't implement it immediately.
Decision 3: Billing Infrastructure
Stripe is the default. Use Stripe Billing with Products and Prices defined in the Stripe dashboard, synced to your database via webhooks. The most common mistake founders make is storing subscription state in their own database without a robust webhook handler — leading to free users who should be paying, or locked-out users who have paid.
Our billing setup: Stripe Checkout for subscription initiation, webhooks for every subscription event (created, updated, cancelled, payment failed), a local subscriptions table that mirrors Stripe state, and a customer portal link for self-service plan changes. Build this before anything else because billing bugs discovered post-launch are extremely damaging to trust.
Decision 4: Data Model Design
Spend a disproportionate amount of time on your data model before writing application code. A clean, well-normalised schema with good indexing is trivial to build applications on top of. A poorly-designed schema infects every feature you build and every query you write.
Key principles we follow: use UUIDs not integer IDs (safer for public exposure), add created_at and updated_at to every table (you'll need them), define foreign keys with CASCADE rules explicitly, and never store arrays in a single column if you'll ever need to query individual elements.
Decision 5: API Design and Versioning
Build your product on top of your own API. Even if you don't plan to expose a public API, building your frontend against an internal REST or tRPC API has several advantages: it makes your frontend and backend independently deployable, it makes testing easier, and it prepares you for future integrations.
We use tRPC for internal type-safe APIs in Next.js projects, and OpenAPI 3.1 for public-facing APIs. Version from day one (/api/v1/...) even if you only have one version — retrofitting versioning after the fact is painful.
Decision 6: Background Jobs and Queues
Every SaaS product eventually needs background processing: sending emails, processing uploaded files, generating reports, running scheduled tasks. Don't handle these synchronously in API routes.
For early-stage products, we use BullMQ with Redis. It's easy to set up, battle-tested, and scales to millions of jobs per day. The key patterns: idempotent jobs (safe to retry), dead letter queues for failed jobs, and monitoring with Bull Board. For serverless deployments, Inngest is an excellent alternative that handles the infrastructure complexity.
What We Deliberately Leave for Later
In 90 days, we ship: core product functionality, auth, billing, basic analytics, and a polished onboarding flow. We explicitly defer: advanced admin tooling, A/B testing infrastructure, internationalisation, and complex permission models beyond basic role-based access. These are real needs, but they're not day-one needs — and treating them as such is how projects blow past deadline.
The goal of a 90-day MVP is to get paying customers. Every architectural decision is evaluated through that lens: does getting this right now meaningfully increase our chance of getting to paying customers faster? If yes, do it right. If no, do the minimum viable version and revisit.