Why we stopped reaching for microservices in MVPs
Distributed systems for a 7-day spec is almost always a mistake. The shape of "monolith first" we run, and when it bends.
There is a moment in every early-product conversation where someone โ usually a founder, sometimes an engineer they've been talking to โ says "we'll probably want a microservices architecture." The sentence is always reasonable-sounding. It's also almost always wrong for an MVP.
We run monoliths. We run them deliberately, not as a fallback. A 7-day sprint has no business building a distributed system, and a lot of the trouble we see in early-stage products started with someone optimising for a scaling problem they didn't have.
What people mean by "microservices"
The word is slippery. At least three different things get called microservices, and they have very different cost profiles.
Separate repos. One for the frontend, one for the backend, sometimes one for the admin. Splitting a single app across repos before there's a single team using it adds a synchronisation cost to every single pull request.
Separate services. The frontend calls a backend service, which calls a different backend service, which reads from its own database. Each service has its own deploy, its own environment variables, its own observability surface, and at least one network hop between them.
Actual service-oriented architecture. A fleet of services, each owned by a team, each with its own SLAs and release cadence. This is the kind that actually earns its complexity in a large organisation.
Most founders, when they ask about microservices, are imagining the last one. What they tend to get built, if they push for it early, is the first or the second โ which carry most of the cost and none of the benefit.
What a monolith actually is, today
A monolith in 2026 is not what the word meant fifteen years ago. The version we ship looks like this:
- A single Next.js application, deployed as one unit.
- Server-side code and client-side code in the same repo, in the same type system, sharing the same validators.
- Long-running work โ scheduled jobs, email sending, webhook processing that shouldn't block a request โ lives in a queue that the monolith produces to, and a small worker that the monolith also knows how to run. Still one deploy, one log stream, one database.
- The database is Postgres, with its own managed host.
- Third-party services โ payments, email, auth, storage โ are not "microservices." They're vendors. We use them.
That's it. This architecture runs happily for thousands of users, integrates cleanly with a dozen third parties, and can be operated by one engineer. It is not primitive. It is appropriately sized.
Why the monolith is faster for a sprint
Every piece of surface area in a distributed system costs sprint time that an MVP doesn't have. Here is where the time actually goes:
Local development
A one-process app runs in one terminal. A two-service app needs a Docker Compose file, a local reverse proxy, and a newcomer-friendly README. We write the README either way, but the Docker side of it will silently break every three weeks because someone upgraded their OS. Every hour fixing it is an hour not on the product.
Networking
Two services mean a call surface between them. Every call needs timeouts, retries, error handling, and an auth model. It also needs an environment variable for the other service's URL, which needs a different value per environment, which needs to be documented, which needs to be kept in sync with the other service's deploys. None of this is engineering work for the user. All of it is engineering work.
Deploys
Two deploys mean two pipelines, two staging envs, two production envs, and at least one conversation per sprint about whether the order of deploys matters for a particular change. It usually does.
Observability
A bug in a monolith is one log stream and one stack trace. A bug across two services is distributed tracing, or a lot of squinting at two logs with timestamps that are slightly out of sync. For a three-person team, the monolith wins before the second service is even built.
Hiring
A codebase that looks like a standard Next app is a codebase that any competent engineer can pick up next month. A codebase that's split across three services with a message bus between them is a codebase that requires an onboarding plan. Founders sometimes underestimate this one. It matters.
The argument for early microservices, and why we disagree
The case people make for splitting services early goes roughly like this: "we'll have to split eventually, so starting split is cheaper." It sounds like engineering prudence. It's almost always false.
You'll split the wrong lines. The natural service boundaries in a product only become visible when the product has real users and real features. Splitting early commits you to a boundary you'll regret. Splitting late is a refactor; splitting wrong is a rewrite.
Splitting later is cheaper than people expect. A well-modularised monolith โ one where the code is organised into clear domain folders and the database is accessed through a thin layer โ can be split into services surprisingly cleanly when the time comes. "Extract service" is a weekend of work if the monolith was written with reasonable hygiene. It's six months of work if someone spent the first year avoiding it on the assumption that splitting later would be painful.
You don't actually split because of scale. Most teams that do split eventually split for organisational reasons โ different teams, different release cadences, different on-call rotations. Those reasons don't exist at three people. Optimising for them before they exist is building infrastructure for an imaginary org chart.
When the answer flips
We don't pretend the monolith is the right answer for everything. There are specific product shapes where services earn their keep from day one. The ones we've actually seen, rather than the ones people worry about:
A real-time workload isolated from the rest. If you're building a product that includes a live collaborative editor or a low-latency realtime channel, putting that workload on a dedicated service with its own scaling model is often correct on day one. The rest of the product stays monolithic.
An AI inference workload. Long-running inference, especially with GPU instances, does not fit cleanly inside a normal web deploy. A separate service with its own scale-to-zero story is appropriate. Again โ the rest of the product stays monolithic.
A heavyweight background pipeline. If the product regularly processes large uploads, renders videos, or runs multi-minute jobs, a worker fleet is a proper service. It still reads from the same database. It just has a different deployment shape.
A compliance boundary. Regulated handling of data โ PCI, certain healthcare contexts โ sometimes requires isolation that's simpler to achieve as a separate service with its own deploy posture.
Notice what's missing from that list: "we might have a lot of users one day," "the backend will get complex," "we want to move fast." Those aren't reasons. Those are why we use a framework, not why we split one.
The monolith we'd be ashamed of
A monolith-first philosophy is not a license to write badly. A monolith that's earned its place has shape to it:
- Domain modules. Billing code lives in one folder and isn't imported from twenty others. User code lives in another. If two modules need each other, there's an interface between them, not a direct reach-in.
- A thin data layer. All database access goes through a small, clear set of functions. You never have a raw query buried inside a React component.
- Async work explicit. Anything that takes more than a second โ email, webhooks out, third-party sync โ goes through a queue, not a "please don't await this" comment.
- Feature flags. A minimal flag system so risky changes can be released without coupling release to deploy.
- A boundary for third parties. Stripe calls, email calls, etc. go through a tiny adapter so the rest of the code doesn't care that it's Stripe today.
That's what "well-factored monolith" means to us. It's not fancy. It's also not a compromise. It's what a product that might one day need to split should look like in the meantime.
The honest trade-off
We think monolith-first is the right call for MVPs roughly 95% of the time, and we're happy to say so out loud. The other 5% are the ones where we push back on our own default, because the product really does need something different on day one.
If you're in the 5%, we'll tell you in discovery and design around it. If you're in the 95%, we'll ship you a monolith that earns its keep for years. What we won't do is build something more complicated than the product needs on the theory that complication is sophisticated. It isn't. A smaller engine is a faster sprint, a simpler handover, and a cheaper business.