Monolith vs Monorepo vs Micro-Frontend Architecture
Three words that sound alike, get confused constantly in interviews and team meetings, and actually answer three completely different questions. This guide untangles them for good, with real production examples from Amazon, Google, Netflix, Spotify and more.
Imagine three people arguing about houses. One is talking about whether a building has one giant room or many small rooms. Another is talking about whether the blueprints for every building in the neighborhood live in one folder or a hundred scattered folders. The third is talking about whether the front door, the kitchen, and the garage were built by three different construction crews who never talk to each other, yet the house still looks like one house from the street. All three people are technically discussing “how a building is organized,” but they are answering entirely different questions. This is exactly what happens when engineers say “monolith,” “monorepo,” and “micro-frontend” in the same sentence. They sound like siblings. They are not even the same species. This guide walks through each one from the ground up, shows how they actually work under the hood, and then puts them side by side so the differences become impossible to forget.
Chapter 01
CCore Concepts: Three Different Questions
Before comparing anything, each term needs its own clean definition. Mixing them up is the single most common mistake people make when this topic comes up in a design discussion or an interview.
What is a Monolith?
A monolith is a way of building the runtime architecture of an application. It means the entire application — the user interface logic, the business rules, the database access, the payment processing, the notification sending — is built, packaged, and deployed as a single unit. When you start the application, one process starts, and that one process does everything. There is no “billing service” running separately from the “user service.” It is all one program, compiled or bundled together, running on one set of servers (though those servers can be scaled to many copies of the same single program).
Think of a monolith like a Swiss Army knife. It is one physical object. The scissors, the knife blade, the bottle opener, and the toothpick are all folded into a single handle. You cannot use just the scissors without carrying the whole tool. If the hinge breaks, every single tool on it becomes harder to use, because they are all physically connected.
What is a Monorepo?
A monorepo is a way of organizing source code storage, not a way of running an application. It means multiple projects — which could be a website, a mobile app, a backend service, and a shared design-system library — all live inside a single version control repository (like one giant Git repository), instead of each project having its own separate repository. Crucially, the projects inside a monorepo do not have to be a single running program. They can be five completely independent applications that are deployed separately, tested separately, and owned by five different teams. The only thing that is “mono” (meaning “one”) is the folder that holds all the source code.
A good analogy is a shared filing cabinet in an office. One filing cabinet (the monorepo) can hold folders for Marketing, Legal, Engineering, and Finance. Each folder is a completely separate department with separate work, separate people, and separate purposes. They just happen to be stored in the same piece of furniture so that anyone can quickly walk over and grab a document from any department without requesting a new cabinet.
What is a Micro-Frontend?
A micro-frontend is a way of building the user interface of a web application by splitting it into smaller, independently built and independently deployed pieces, which are then stitched together in the browser (or on a server) to look like one seamless website to the end user. For example, the header/navigation bar might be built and deployed by Team A, the product search page by Team B, the checkout flow by Team C, and the “My Account” section by Team D — each with its own codebase, its own release schedule, and sometimes even its own frontend framework — yet the customer sees a single, unified website.
Picture a shopping mall. Each store (Nike, Starbucks, the movie theater) is built, staffed, stocked, and renovated completely independently by different companies. Starbucks does not need permission from Nike to change its menu. Yet when a shopper walks through the mall, it feels like one connected experience, with shared hallways, shared parking, and shared signage tying it together.
Monolith describes how the running application is packaged. Monorepo describes where the source code lives. Micro-frontend describes how the user interface is split and assembled. A team can mix and match these in almost any combination — that surprises a lot of people the first time they hear it.
Monolith
One deployable unit running the whole application as a single process or cluster of identical processes.
Monorepo
Many independent projects, one shared version-control repository storing all of their code.
Micro-Frontend
Many independently deployed UI fragments composed together into one experience for the end user.
Monorepo + Microservices
Many small backend services, each independently deployable, but all stored in one shared repository.
Because these three ideas sit on completely different axes, they can be combined freely. A company can run a monolith whose entire source code lives in a monorepo. A company can run twelve microservices whose code is split across twelve separate repositories (a “polyrepo”), or all twelve stored in a single monorepo. A company can build a micro-frontend UI that talks to a backend monolith, or a micro-frontend UI that talks to dozens of microservices. There is no rule that forces these choices to travel together, even though people often assume “modern” architecture means picking the split option for all three at once.
Chapter 02
IInternal Working
Definitions are only useful once you can picture what actually happens when the system runs, builds, and deploys. This chapter opens the hood on all three.
How a Monolith Works Internally
Inside a monolith, all the different pieces of business logic — say, “user accounts,” “shopping cart,” and “order processing” — exist as different modules, classes, or folders inside the same codebase. But because they are compiled or bundled into a single artifact (a single executable file, a single container image, or a single deployable package), they share the same memory space at runtime. When the “shopping cart” module needs to check if a user is logged in, it does not send a network request across the internet to a separate “user service” — it simply calls a function directly, in-process, which is extremely fast because there is no network hop involved.
This single artifact is typically deployed to a fleet of identical servers behind a load balancer. Every server runs the exact same copy of the entire application. If the “shopping cart” logic gets slow under heavy traffic, the only way to give it more resources is to add more copies of the entire monolith, even though the “user accounts” logic that is bundled with it did not need any extra capacity at all. This is one of the defining internal characteristics of a monolith: scaling is done for the whole application, not for one piece of it.
How a Monorepo Works Internally
Internally, a monorepo relies heavily on tooling that most single-project repositories do not need. Because dozens or hundreds of projects share one repository, specialized build systems (such as Bazel, Nx, or Turborepo) are used to figure out exactly which projects were affected by a given code change, so that the system does not waste time rebuilding and retesting every single project every time anyone changes one line of code. This is called “affected graph” analysis — the tool builds a dependency graph of which project depends on which other project, and when a file changes, it walks that graph to find every project that needs to be rebuilt.
A monorepo also typically uses build caching, where the output of a build step is saved and reused if the same inputs are seen again, sometimes even shared across different developers’ machines through a remote cache. Access control inside a monorepo is usually handled with “code ownership” files that specify which team must approve changes to which folder, since without this, anyone could accidentally (or carelessly) edit code belonging to a completely different team.
How a Micro-Frontend Works Internally
A micro-frontend system needs a “shell” or “container” application — a lightweight piece of code whose only job is to decide which micro-frontend to load for a given URL or page section, fetch it, and mount it into the page. There are a few common internal techniques for this: build-time integration (each micro-frontend is published as a package and combined into one build before deployment), run-time integration via JavaScript (the shell dynamically downloads separate JavaScript bundles in the browser and renders them, often using a technique called Module Federation), run-time integration via iframes (each micro-frontend runs inside its own isolated iframe), and server-side composition (a server assembles HTML fragments from different backend services before sending the final page to the browser, sometimes called “edge-side includes”).
Whichever technique is used, the shell also needs a strategy for shared concerns: how do micro-frontends avoid loading five separate copies of the same UI framework, how do they navigate between each other without a full page reload, and how do they communicate (for example, telling the “cart icon” micro-frontend in the header to update its count after the “checkout” micro-frontend completes a purchase)? These problems are usually solved with a shared event bus, shared browser storage, or a common design-system library that every micro-frontend imports.
graph TB
subgraph Monolith["Monolith Runtime"]
LB1[Load Balancer] --> A1[App Instance 1]
LB1 --> A2[App Instance 2]
A1 --> M1[User Module]
A1 --> M2[Cart Module]
A1 --> M3[Order Module]
M1 M2
M2 M3
A1 --> DB1[(Single Database)]
end
Fig 1 — Inside a monolith, modules call each other directly in memory and share one database, but the whole bundle scales together.
graph LR
subgraph Repo["Monorepo (Single Git Repository)"]
direction TB
P1[Project: Web App]
P2[Project: Mobile App]
P3[Project: Backend API]
P4[Project: Shared UI Library]
P4 --> P1
P4 --> P2
P3 -.independent deploy.-> Prod1[Production]
P1 -.independent deploy.-> Prod2[Production]
end
Fig 2 — A monorepo stores unrelated, independently deployed projects side by side, sharing common libraries without sharing a runtime.
graph TB
User[Browser] --> Shell[Shell / Container App]
Shell --> MF1[Micro-Frontend: Header]
Shell --> MF2[Micro-Frontend: Product Search]
Shell --> MF3[Micro-Frontend: Checkout]
MF1 -.deployed by Team A.-> D1[Independent Pipeline]
MF2 -.deployed by Team B.-> D2[Independent Pipeline]
MF3 -.deployed by Team C.-> D3[Independent Pipeline]
Fig 3 — The shell app composes independently built and independently deployed UI fragments into one page for the user.
Chapter 03
DData Flow & Lifecycle
Understanding what happens step by step — from a developer writing code to a user seeing a screen — makes the differences concrete.
Request Lifecycle in a Monolith
A user clicks “Buy Now.” The browser sends one HTTP request to the monolith’s server. The single application process receives it, routes it internally to the correct piece of code (no network call needed), that code checks the user’s identity, checks inventory, processes payment, updates the order table, and returns a response — all within one process, often within a single database transaction that can guarantee everything succeeds or fails together. This is a major internal advantage: a monolith can often wrap an entire business operation in one atomic transaction, because everything touches the same database connection.
Build and Deploy Lifecycle in a Monorepo
A developer changes a shared button component. They commit the change to the monorepo. The build system’s dependency graph identifies every project that imports that button — say, three different apps. The continuous integration pipeline automatically rebuilds and retests only those three apps (thanks to affected-graph analysis), skips the other ninety-seven untouched projects to save time, and if tests pass, each affected app can be deployed on its own independent schedule. Nothing here requires the three apps to be part of the same running program — the monorepo lifecycle is entirely about code storage, builds, and testing, not runtime behavior.
Page Load Lifecycle in a Micro-Frontend System
A user navigates to a webpage. The browser loads the lightweight shell application first. The shell looks at the URL, decides which micro-frontends are needed for this page, and fetches each one — possibly from completely different servers, built by completely different teams, on completely different release schedules. Each micro-frontend renders into its assigned slot on the page. If the user then clicks into the checkout section, the shell may fetch and mount an entirely different micro-frontend on the fly, without reloading the whole page. Throughout this, shared state (like “is the user logged in”) typically flows through a shared mechanism the shell provides, so the independently built pieces stay in sync.
Monolith: Single Hop
Browser to server to one process to one database, then straight back — no internal network calls.
Monorepo: Change to Selective Rebuild
Code change triggers a dependency-graph scan, rebuilding only affected projects, then independent deployment per project.
Micro-Frontend: Runtime Assembly
Shell loads first, discovers and fetches independently deployed fragments, and stitches them into one page live in the browser.
A monolith’s lifecycle is like a single chef cooking an entire meal alone in one kitchen — fast coordination, but the whole kitchen has to be scaled if any one dish becomes popular. A monorepo’s lifecycle is like a cookbook publisher who only reprints the specific recipe pages that changed, instead of reprinting the entire book every time one ingredient list is corrected. A micro-frontend’s lifecycle is like a food court where each stall cooks and serves independently, but a food-court map (the shell) tells customers where to walk to get each dish, and the stalls open and close on their own schedules.
Chapter 04
AAdvantages, Disadvantages & Trade-offs
Every one of these choices trades something away to gain something else. None of them is universally “better” — they are better for different situations.
Monolith Trade-offs
Advantages
- Simple to develop, test, and deploy when the team and codebase are still small
- Function calls between modules are fast because there is no network involved
- Easier to guarantee data consistency with a single database transaction
- One deployment pipeline, one set of logs, one place to debug
Disadvantages
- The whole application must be rebuilt and redeployed even for a tiny change
- Scaling means scaling everything together, even the parts that do not need it
- As the codebase grows, it becomes harder for many teams to work in it without stepping on each other
- A bug or crash in one module can potentially bring down the entire application
Monorepo Trade-offs
Advantages
- Sharing code between projects is trivial — no need to publish and version internal packages
- One coordinated view of every project makes large cross-cutting changes easier
- Consistent tooling, linting, and standards across all projects
- Easier to see the “true” dependency graph of an entire company’s codebase
Disadvantages
- Requires specialized tooling (affected-graph builders, remote caching) to stay fast as it grows
- A repository can become enormous, slowing down basic operations without the right tooling
- Weak access-control discipline can let any engineer accidentally touch any project
- A misconfigured build step can, in the worst case, block unrelated teams from shipping
Micro-Frontend Trade-offs
Advantages
- Different teams can build, test, and release their piece of the UI independently
- Teams can choose different frontend frameworks or upgrade at their own pace
- A failure or slowdown in one micro-frontend can, if isolated correctly, avoid breaking the whole page
- Large organizations can scale frontend development the way they already scale backend teams
Disadvantages
- Users can end up downloading duplicate copies of shared libraries, hurting page-load performance
- Keeping a consistent visual look and feel across independent teams requires real discipline
- Cross-fragment communication and shared state are genuinely hard problems
- Operationally more complex: more pipelines, more monitoring dashboards, more moving parts
Chapter 05
DDesign Patterns & Anti-Patterns
Each architecture has well-known patterns that make it succeed, and well-known anti-patterns that quietly turn it into a maintenance nightmare.
Monolith Patterns
The modular monolith pattern keeps a single deployable unit but enforces strict internal boundaries between modules — for example, the “billing” module is not allowed to directly touch the “inventory” module’s database tables, and can only talk to it through a defined internal interface. This gives many of the organizational benefits of separated services while keeping the operational simplicity of one deployment. This pattern is often used as a deliberate stepping stone toward microservices later, if and when it becomes necessary.
Description
A monolith where modules directly reach into each other’s internal data structures and database tables with no defined boundaries at all, so that changing one part of the code unpredictably breaks unrelated parts.
Why It Happens
Under deadline pressure, developers take the “quick” shortcut of reaching straight into another module instead of going through a proper interface, and over time these shortcuts multiply.
How To Avoid It
Enforce module boundaries with tooling (for example, restricting which folders can import from which other folders) and code review discipline from the very start of the project.
Monorepo Patterns
The trunk-based development pattern is common in monorepos — everyone commits frequently to one main branch instead of maintaining many long-lived feature branches, which avoids painful merge conflicts across hundreds of projects. Pairing this with codeowners-based review routing (where the tool automatically assigns the right reviewers based on which folder changed) keeps a huge shared repository manageable.
Description
A monorepo that rebuilds and retests every single project on every single commit, regardless of what actually changed.
Why It Happens
Teams adopt a monorepo for its code-sharing benefits but skip investing in dependency-graph-aware build tooling and caching.
How To Avoid It
Adopt an affected-graph build system and remote caching from day one — retrofitting it after hundreds of projects exist is far more painful.
Micro-Frontend Patterns
The vertical slice pattern assigns each team a complete business capability (for example, one team fully owns “search,” including its UI, its backend service, and its data) rather than splitting purely by UI component, which reduces the amount of cross-team coordination needed for any single feature. A shared design-system package is almost always used to keep buttons, colors, and typography consistent across independently built fragments.
Description
Micro-frontends that are technically deployed separately but are so tightly coupled — sharing internal state directly, requiring simultaneous releases, breaking whenever another team changes something — that they behave like one fragile monolith anyway, minus the simplicity.
Why It Happens
Teams split the UI into pieces without first agreeing on stable, versioned contracts for how those pieces communicate.
How To Avoid It
Define clear, stable communication contracts (shared events, well-documented shared state) up front, and treat those contracts with the same seriousness as a public API.
Chapter 06
BBest Practices & Common Mistakes
Knowing the theory is one thing. Avoiding the mistakes real teams make in production is another.
Start Modular From Day One
Organize code into clearly separated modules even inside a single deployable, so a future split into services (if ever needed) is far less painful.
Splitting Too Early
Breaking a small application into microservices before there is a real scaling or organizational problem to solve, adding operational complexity for no benefit.
Invest in Tooling Early
Set up affected-graph builds, caching, and code-ownership rules before the repository grows large enough for their absence to hurt.
Treating It Like a Free Pass to Couple Everything
Just because two projects share a repository does not mean they should share deployment schedules or become secretly dependent on each other’s internals.
Define Ownership by Business Capability
Give each team a full vertical slice of functionality rather than an arbitrary UI fragment, so coordination overhead stays low.
Ignoring Shared Bundle Size
Letting every micro-frontend bundle its own full copy of a UI framework, silently making page load times far worse for end users.
A Practical Decision Checklist
| Question | Leans Toward |
|---|---|
| Is the team small (fewer than roughly 10-15 engineers)? | Monolith |
| Do multiple teams need to share code without publishing packages? | Monorepo |
| Do multiple frontend teams need independent release schedules? | Micro-Frontend |
| Does one part of the system need to scale far more than the rest? | Splitting that part out (monolith → services) |
| Is cross-team coordination overhead already a major pain point? | Micro-Frontend or Microservices, with a Monorepo to ease sharing |
Chapter 07
RReal-World & Industry Examples
These are not just textbook ideas — major companies have made deliberate, well-documented choices among them.
Shopify — The Modular Monolith
Shopify famously runs one of the largest Ruby on Rails monoliths in the world, deliberately choosing a “modular monolith” approach with strict internal boundaries between components, rather than splitting into microservices, because it kept development fast for their scale while avoiding excessive distributed-systems complexity.
Google — The Original Monorepo at Massive Scale
Google stores the vast majority of its internal code — billions of lines, across thousands of projects — in a single company-wide monorepo, using custom-built tooling for affected-graph builds and code review routing, proving the monorepo pattern can scale to an enormous organization with the right investment in tooling.
Meta and Microsoft — Monorepo Tooling in the Open
Both companies open-sourced monorepo build tools (Meta contributed to Buck-style tooling and Microsoft-adjacent teams popularized tools like Nx and Rush in the JavaScript ecosystem), which shows how seriously large engineering organizations take the tooling side of the monorepo trade-off.
Spotify and IKEA — Micro-Frontends in Production
Spotify’s desktop application has historically been composed of independently developed feature modules loaded into a shared shell, allowing different squads to ship features on their own schedule. IKEA has also publicly discussed using micro-frontend techniques to let regional and feature teams independently manage sections of their e-commerce site.
Amazon — Independent Service Ownership
Amazon is widely known for moving away from an early monolithic retail application toward independently owned and independently deployed services, driven by the need for different teams to release at different speeds without waiting on one shared deployment train — a philosophy that also underlies why many Amazon-style organizations later adopt micro-frontends on the UI side.
Chapter 08
FFrequently Asked Questions
Chapter 09
SSummary and Key Takeaways
What to Remember
- Monolith is a runtime packaging choice: the whole application deployed and run as a single unit.
- Monorepo is a source-control choice: many independent projects stored in one shared repository.
- Micro-Frontend is a UI architecture choice: independently built and deployed frontend pieces composed into one experience.
- These three ideas sit on different axes and can be freely combined — none of them requires or excludes the others.
- A modular monolith often gives most of the organizational benefits of splitting up, without the full operational cost of doing so.
- Monorepos need real investment in tooling (affected-graph builds, caching, code ownership) to stay fast as they grow.
- Micro-frontends solve a team-scaling and independent-release problem, not a performance problem — and can hurt performance if bundle duplication is not managed.
- Choose based on team size and organizational pain today, not on which pattern is currently fashionable.