Building a SaaS MVP: The Tech Stack Choices That Come Back to Haunt You
TL;DR — Most SaaS MVPs don't die from the wrong language or the wrong cloud. They die from three decisions made in week one that cost six months to undo: custom authentication, Firestore as the primary datastore, and premature service decomposition. This post is a decision framework for getting those three right — and a clear-eyed look at what you can actually cut corners on.
This article is for:
- Founders or CTOs at the 0→1 stage deciding how to build a SaaS product
- Engineers handed a greenfield SaaS spec and told to "pick the stack"
- Teams that built an MVP and are now experiencing the consequences of early technical decisions
This article is NOT for:
- Teams already past product-market fit looking to scale infrastructure — the decisions at that stage are different
- Readers wanting a tutorial or boilerplate recommendation; this is a decision framework, not a starter kit guide
- Projects where "MVP" means a marketing site with a waitlist — this is about production SaaS applications with auth, billing, and data
The three questions that determine whether your stack survives year one
In SaaS MVP development, the stack question is rarely "which framework is best?" The better question is: which early decisions will be expensive to reverse after customers, payments, permissions, and production data exist?
Before you pick a framework, a database, or a hosting provider, there are three questions worth running every technology decision through:
- Can you change this decision in a weekend? If yes — reversibility is high, the choice matters less. Pick the thing your team knows.
- Will this break the moment you have 100 concurrent users? If yes — you haven't built an MVP, you've built a proof of concept. Know the difference.
- Does this force a full rewrite before you can add feature X? If yes — and feature X is on your 6-month roadmap — you've built a trap.
Most of the bad decisions in early SaaS products fail test three. Teams don't choose Firebase because it can't handle 100 users — it can. They choose it because it's fast to start. They discover the problem 8 months later when "add an admin reporting panel" requires a data model that Firestore can't express cleanly. The question was never about scale. It was about lock-in.
The decisions that cost you twice
Note: Timeframes in this article are BrainFeed delivery estimates from real MVP and post-MVP rebuild work, not vendor benchmarks.
Custom authentication
This is the single most common mistake in SaaS MVPs, and the one with the most predictable consequences.
Rolling your own authentication in 2026 looks like this: 2–3 days to build login, registration, and password reset. It works. You ship. Then, 4 months later, an enterprise prospect asks for SSO. You spend 3 weeks building SAML integration. Then a SOC 2 audit surfaces that your session tokens are stored incorrectly. Another week. Then a compliance requirement means you need MFA — another week. Every one of these is a solved problem in Clerk, Auth0, or Supabase Auth. The combined cost of implementing them from scratch is routinely 6–8 weeks of engineering time that buys you exactly what the library provides for free at MVP scale.
The argument against managed auth is almost always "we want control." The honest question is: control over what? The cryptographic primitives? The session management logic? These are the parts you most want to not be responsible for. The parts you actually want control over — which users can access which features, how you model teams and permissions — are application-layer concerns that every auth library leaves entirely in your hands.
What breaks in production without it: A customer resets their password and doesn't get the email because your SMTP setup is misconfigured. A session token doesn't expire correctly. A user with a deactivated account can still access the API because the middleware check is wrong. Each of these is a support ticket, a refund conversation, or — at the wrong moment — a security incident.
Use Clerk if you want the best developer experience and built-in UI components. Use Supabase Auth if you're already on the Supabase stack. Use Auth0 if you have an enterprise requirement for it. The specific choice matters less than the principle: do not write authentication logic in 2026 for an MVP.
Firestore as your primary datastore
Firebase is genuinely excellent technology. Real-time sync, generous free tier, auth, storage, and serverless functions in a single console. It is the right choice for a category of products.
The trap is using it as a substitute for a relational database when your product has relational data — which most SaaS products do.
The migration moment arrives reliably at month 6–9 when you need one of the following:
- A reporting page that aggregates data across multiple entity types (users, subscriptions, activity)
- Multi-tenancy — isolating one customer's data cleanly from another's at the query level
- Complex filtering or search — Firestore's query model breaks down on compound filters across unbounded document sets
- Background jobs that process records in bulk
- An admin panel for your team to manage customer data
All of these are standard features of a post-MVP SaaS product. All of them are painful in Firestore and natural in Postgres. The migration from document store to relational database is not a refactor — it is a rebuild of your data model, query layer, and often your API contracts. Teams who build on Firebase and then need to move typically spend 6–10 weeks on data migration alone, while also shipping features to retain customers.
The counterpoint: Firebase is not wrong. It's wrong when used as the primary datastore for an app that will eventually need SQL semantics. Use it for what it's genuinely good at: real-time presence, push notifications, file storage. Your core application data belongs in Postgres.
When Firebase/Firestore is still the right call: Use Firestore when the product is primarily real-time, collaborative, offline-first, mobile-heavy, or document-centric — for example chat, presence, lightweight mobile apps, field-data collection, or prototypes where relational reporting is not on the roadmap. The warning above is narrower: do not make Firestore the system of record for a B2B SaaS product that will need reporting, billing joins, tenant-level admin, and complex filters.
Premature service decomposition
The monolith gets a bad reputation. Engineers who have been at mid-stage companies know the pain of a 500,000-line monolith with no domain boundaries — it's genuinely miserable. The response to that pain is often to start the next project with microservices. This is almost always the wrong call at MVP stage.
In our MVP work, a clean modular monolith is usually meaningfully faster to ship than an equivalent microservices setup at the same feature count. The claims that differ — "but Netflix does it," "but we'll need to scale" — don't hold at 0→1. Netflix built a monolith first. Your MVP will not need Netflix's scaling architecture. What it needs is to ship fast enough to find product-market fit before the runway ends.
The real cost of premature microservices is not operational overhead (though that's real — you need service discovery, API gateways, distributed tracing from day one). The real cost is that every new feature now requires coordinating changes across multiple services, multiple deployments, and multiple data stores. A product change that takes 2 days in a monolith takes 5 days when you're crossing service boundaries. Over 6 months, the compounding effect is significant.
The counterintuitive thing: starting as a monolith does not trap you in a monolith. When you have real production load and know where the bottlenecks are — because customers are showing you — you split the specific pieces that need it. Every successful service decomposition story starts with "we had this specific scaling problem" — not "we anticipated we might have this problem someday."
Start as a monolith. Keep the domain boundaries clean (separate folders per domain, no cross-domain direct imports, clear interfaces). Split when you have a specific, measured reason to.
Auth, billing, infra — the order of operations
If you get the sequence wrong, you spend your first year fighting infrastructure instead of finding customers.
1. Auth first
Auth gates everything. Users can't do anything in your product until you know who they are. Get this right in week one and you never touch it again. Get it wrong and you're patching it every time you add a user-facing feature.
The choice isn't "which auth library" — it's "managed auth now, or roll our own and migrate to managed auth later." The migration is always more expensive than doing it right the first time.
2. Billing second
The temptation is to skip this at MVP and bolt it on when you have paying customers. The problem is that billing is not a feature you can add cleanly after the fact. Your user model, your subscription model, and your access control model all depend on billing state. If you add billing as an afterthought, you end up with inconsistent checks scattered across the codebase: "if user.paid" in some places, "if subscription.active" in others, nothing in a third place where it should be.
Use Stripe Billing from the start. The overhead of setting up Stripe Customers, Subscriptions, and webhooks correctly is 2 days, not 2 weeks. The payoff: adding a trial period, an annual plan, or a seat-based pricing model later takes hours, not a rewrite.
What most teams get wrong: they build a thin billing wrapper thinking "we'll migrate to Stripe Billing when we're bigger." The migration never happens at the right time — it happens right before a big customer renewal, or just after a compliance audit, or when the ad-hoc billing logic has grown to 4,000 lines across 30 files.
3. Infra last
Your infrastructure choices should be the least interesting part of the MVP. Use managed options. Railway, Render, or Vercel for hosting. Supabase, Neon, or Railway Postgres for your database. Managed Redis for caching and queues if you need it.
The "but we need to control our infrastructure" argument at MVP stage is almost always misplaced priority. The cost delta between managed and DIY is real money — but it's small against the engineering hours you save. When you have a specific scale problem that managed infrastructure can't solve, you'll have the revenue to solve it properly.
The lock-in matrix
Not every decision is a trap. Some choices feel scary but are cheap to change. Here's an honest assessment of which SaaS decisions are reversible and which ones aren't.
| Decision | Reverse cost | Why |
|---|---|---|
| Authentication system | High — 3–6 weeks | Session storage, password reset flows, SSO, MFA, and user model all tied to provider |
| Database type (SQL vs NoSQL) | Very high — full data migration | Query patterns and data model diverge fundamentally |
| Service architecture (monolith vs microservices) | High going micro early; cheap going micro late | Premature splits create distributed monoliths with no path back |
| Billing provider | Medium — 2–4 weeks | Subscription state, payment methods, webhooks, and customer records |
| Background job system | Low–Medium | Can usually migrate job-by-job if worker interface is stable |
| Frontend framework | Low–Medium — incremental | Rewrite component by component; API contract doesn't change |
| Cloud provider | Low–Medium — containers help | Most complexity is in managed services, not raw compute |
| Styling approach | Low — codemods handle most cases |
The pattern: persistence decisions (database, auth, billing) have high reverse costs. Rendering decisions (framework, styling) are cheap to change. This is where to concentrate your deliberate choices.
What the stack should look like in 2026
If you're building a greenfield SaaS MVP today, this is the stack I'd recommend — not because it's the only right answer, but because each choice optimises for time-to-ship while keeping the high-cost decisions reversible.
Language: TypeScript throughout. Full-stack TypeScript reduces context switching and catches the type of data-shape mismatch bugs that cost hours to debug at 2am.
Framework: Next.js for full-stack products where the frontend and backend are tightly coupled. If you need a clear API/frontend separation — for a mobile client, third-party integrations, or a complex backend — use Fastify or Express for the API and React or Next.js for the frontend.
Database: Postgres. Railway Postgres or Supabase Postgres for managed hosting. If you want query observability from the start, Neon's branching feature makes staging environments much cleaner. Not because MySQL is bad — it is not — but Postgres is the better default for SaaS products that need relational modelling, reporting, JSON flexibility, extensions, and eventually row-level tenancy controls. Not MongoDB unless your product is genuinely document-native; not Firestore as your primary datastore for relational SaaS data.
ORM: Drizzle (better TypeScript integration, closer to raw SQL) or Prisma (more ergonomic, larger community). The choice matters less than using one consistently.
Auth: Clerk if you want built-in UI components and the smoothest developer experience. Supabase Auth if you're already on Supabase. Either is correct. Neither is "too much" for an MVP — both have generous free tiers.
Billing: Stripe Billing with Stripe Customer Portal. Not a custom billing implementation, not a wrapper library. Stripe's documentation for subscriptions is excellent, and the Customer Portal saves you the entire "manage my subscription" UI surface.
Transactional email: Resend. Clean API, TypeScript-native, and straightforward deliverability.
Background jobs: Inngest or Trigger.dev for anything that needs to run outside a request/response cycle. Both are TypeScript-native and have durable execution — jobs survive deploys and retries are built-in. Not DIY cron, not a queue you manage yourself.
Error monitoring: Sentry from day one. Not "when we have enough users to justify it." You want the first crash report, not the tenth. The free tier handles MVP volumes.
Deployment: Vercel for the Next.js application. Railway or Render for long-running services and background workers. Add Upstash for serverless Redis if you need caching or a queue.
The decision you'll thank yourself for most
If there's one non-obvious piece of advice that comes from watching SaaS products reach month 12 successfully versus stalling:
Treat your data model as a first-class design artifact before you write a line of application code.
Not your UI flows. Not your API routes. Your data model — the entities, the relationships, how multi-tenancy is expressed, where ownership boundaries sit. Teams that spend even four hours on this in week one avoid the largest class of late-stage rewrites.
The most common data model mistake is building a single-tenant schema and adding multi-tenancy later. Here's what the correct approach looks like from day one in Drizzle:
// Every resource table carries an organisationId — the tenancy boundary
export const projects = pgTable("projects", {
id: uuid("id").primaryKey().defaultRandom(),
organisationId: uuid("organisation_id")
.notNull()
.references(() => organisations.id, { onDelete: "cascade" }),
name: text("name").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
// Row-level filtering is enforced at the query layer, not sprinkled per-endpoint
export function getProjects(db: DrizzleDb, organisationId: string) {
return db
.select()
.from(projects)
.where(eq(projects.organisationId, organisationId));
}
The failure mode is skipping organisationId on the first pass ("we only have one customer") and adding it later. "Later" means writing a migration that touches every row in the table, updating every query to add the filter, and auditing every place where you might have accidentally exposed one customer's data to another. That's a full week of careful, high-risk work on a live production database. The right schema on day one costs 10 minutes.
A SaaS product's data model is the decision that can't be refactored around. Everything else — auth, billing, infra, framework — is table stakes that managed tooling largely solves. The data model is yours, it's unique to your product, and getting the foundational decisions wrong there is the real technical debt that accrues from an MVP.
SaaS MVP tech stack FAQs
Should a SaaS MVP start with microservices?
Usually, no. Start with a modular monolith, keep domain boundaries clean, and split services only when you have a specific production bottleneck.
Is Firebase good for a SaaS MVP?
Firebase and Firestore can be excellent for real-time, mobile-heavy, collaborative, or document-centric products. They are a weaker default for B2B SaaS products that need relational reporting, billing joins, tenant-level admin, and complex filters.
What database should a SaaS MVP use?
For most B2B SaaS MVPs, Postgres is the safest default because it handles relational data, reporting, JSON flexibility, and future tenancy controls well.
Should billing be added after the MVP?
No, not for a real SaaS product. Billing state affects access control, subscription logic, trials, plans, and permissions, so it should be designed early even if pricing changes later.
What to do next
If you're evaluating a SaaS build — your own or with an agency — the questions that matter are less about which framework and more about these three:
- How is multi-tenancy expressed at the data layer?
- What happens to the billing integration when you add a new pricing model?
- If you need SSO for an enterprise customer in month 9, how many weeks does that take?
A team that can answer those questions confidently in the first conversation has built SaaS products before. One that pivots immediately to "we use Next.js" or "we're on AWS" probably hasn't.
We build SaaS products at BrainFeed — from greenfield MVPs to production systems with auth, billing, workflows, admin panels, and real payment volume. If you want a second opinion before locking your stack, the SaaS development page explains how we structure MVP builds and technical discovery.




