Home / Articles

Choosing a React Folder Structure: Seven Layouts and Their Breaking Points

Compare flat, type-based, feature-based, Atomic Design, DDD, Feature-Sliced Design and monorepo layouts for React, and learn the scale at which each one stops working.

Publisher: REACTAPP.TOP31 min read7020 words

This article is published in English.

Sources: React Folder Structure: 7 Ways to Organize a React App (And Exactly When Each One Breaks)

Every growing React codebase eventually hits the same question: where should this file go? There is no official answer, yet teams spend whole sprints migrating between layouts and interviewers still probe candidates on it. This guide compares seven common ways to organize a React application, from a flat src/ folder to a multi-app monorepo, using one reference app throughout. For each layout you will see what it is, who genuinely uses it, what it does well, where it hurts, and the concrete point at which it stops working, so you can pick the cheapest structure that fits the app you actually have.

Nobody official will choose for you

React's legacy documentation once had a page about file structure. It began by stating that React has no opinion on how files are placed into folders, and it advised anyone starting a project not to spend more than five minutes choosing a layout. The page was later removed when the docs moved to react.dev, and nothing replaced it. Today React effectively says nothing at all about folders. The old guidance survives in the archived FAQ page.

The frameworks and tools around React are just as noncommittal. Next.js describes itself as unopinionated about how project files are organized and colocated. Vite's React template generates a handful of source files and no folders to speak of. No authority in the ecosystem tells you what to do.

That absence explains a lot. Structures such as Bulletproof React have accumulated tens of thousands of GitHub stars largely for proposing a folder layout, and developers argue about the subject constantly. The honest framing for everything that follows is that these seven structures are conventions, not standards. None of them is endorsed by React itself. What matters is understanding each one's costs and, above all, where it breaks, because that is the part that becomes decisive once an application is real.

The two principles underneath every layout

The inventors are more relaxed than the evangelists

People who created these methodologies tend to hold them far more loosely than the people who promote them. Brad Frost, who created Atomic Design, published "Extending Atomic Design" in 2019. In it he describes people contacting him to "correct" his terminology, insists that atomic design is not rigid dogma, and says teams that want nine stages named after members of the band Chicago should go ahead. You can read it in Extending Atomic Design. The creator of the method is telling you that the names do not matter, while somewhere a code review is stalled on whether a component counts as a molecule.

React's old docs also addressed Atomic Design by name. They noted that some developers split components into folders according to their role in the application, cited Atomic Design as an example of that approach, and recommended treating such methodologies as helpful examples rather than strict rules. Coming from React's own documentation, that is the best-sourced criticism of strict Atomic Design in React that exists.

Screaming Architecture

There is really only one substantial argument in this debate, and it is older than React. In September 2011, Robert C. Martin published Screaming Architecture. His question: when someone looks at the top-level directories of your application, do they immediately see a health care system, an accounting system, or an inventory system? His conclusion was that an architecture should tell readers about the system, not about the frameworks it happens to use.

That idea sits beneath every feature-based layout. A src/ containing components/, hooks/, utils/, and services/ announces that this is a React app. A src/ containing checkout/, billing/, and inventory/ announces what the business does. Every structure in this guide is either a variation on that idea or a deliberate rejection of it.

Colocation

The second principle is the one that carries the most weight day to day. Kent C. Dodds calls it colocation: keep code as close as possible to the place where it is relevant. He credits Dan Abramov with a sharper phrasing, which is that things that change together should live as close together as is reasonable. The full argument is in Colocation.

Seen through that lens, every layout in this guide responds to one question: which pieces of your code evolve as a unit? If you answer that correctly, the folders almost design themselves. If you answer it wrongly, no methodology will rescue you.

A note on a popular quote: the advice to "move files around until it feels right" is widely attributed to Dan Abramov and sometimes cited as official React documentation. It is not documentation; the docs contain the five-minute advice instead, and the Abramov line traces back to a tweet that appears to have been deleted. The sentiment is sound; treat the citation as folklore.

One reference app, seven arrangements

Comparing layouts is only fair if the application stays constant; otherwise you end up comparing examples rather than structures. So every layout below organizes the same small e-commerce app with the same files:

  • Features: auth (login and signup), catalog (product list and detail), cart, checkout, and orders.
  • UI primitives: Button, Input, Modal, Badge, and Spinner.
  • Hooks: useAuth, useCart, useProducts, useCheckout, and useDebounce.
  • API modules: authApi, productsApi, checkoutApi, ordersApi, and a shared apiClient.
  • Other: a cartStore and a formatPrice helper.

Four of those files are deliberately awkward, because they reveal where each layout starts to hurt:

  • Modal.jsx gets labeled atom, molecule, or organism depending on the person classifying it, which stresses Atomic Design's taxonomy.
  • useDebounce.js is completely generic, yet only the catalog search uses it. Is it a global hook or a catalog hook?
  • CartTotal.jsx renders inside the cart drawer as well as on the checkout screen, so its owner is unclear.
  • formatPrice.js looks like a trivial utility, unless money is a real domain concept, in which case it should be a Money value object and putting it in a utilities folder is a mistake.

Keep those four in mind. Each structure resolves them differently, and those disagreements are exactly what distinguishes one layout from another.

Flat structure

What it is

Everything sits directly in src/, with no folders or almost none, ordered alphabetically because nothing else imposes an order.

Nobody published this as a methodology. It is simply where every project begins, and where a surprising number stay. React's legacy docs even recommended it as a starting point: if you feel stuck, keep all files in one folder, and once it grows large enough to need splitting you will have learned which files you tend to edit together. That is genuinely good advice. A structure derived from observed change patterns beats one imposed on the first day.

Who uses it

Prototypes, spikes, sandbox demos, hackathon projects, take-home assignments, and internal tools with a single maintainer. Quietly, it also describes plenty of production apps that were never refactored.

The official Vite React template is essentially flat. Running npm create vite@latest -- --template react-ts produces main.tsx, App.tsx, App.css, index.css, and an assets/ folder, with no components/ or features/. Because the scaffold expresses no opinion, the debate simply moves downstream to every team that uses it.

Strengths

  • Navigation costs nothing. There is one folder, and the file picker finds anything by name. With no decisions about placement, there are no arguments about placement either.
  • Refactoring is free. There is no structure to violate and therefore nothing to migrate. You can move quickly, discover what the app really is, and organize once you know.
  • It is honest about uncertainty. In the first week you do not know your feature boundaries, and a flat layout does not pretend you do. Any folder created that early is a guess you may later pay to undo.

Weaknesses

  • It stops scaling abruptly at around 30 files. The failure is not gradual: one day the file list no longer fits in the sidebar and the shape of the app disappears.
  • Alphabetical order carries no meaning. authApi.js sits beside App.jsx purely because both start with A, while useAuth.js and LoginForm.jsx, which you always edit together, end up far apart. The sort order works directly against colocation.
  • There are no boundaries to enforce. Any file can import any other, circular dependencies creep in unnoticed, and by the time you want boundaries every violation is load-bearing.
  • Removing a feature becomes detective work. Deleting checkout means reading files to work out which ones belong to it, because there is no folder to remove.

Where it breaks

At roughly 30 files, or the moment a second developer joins, and the second threshold matters more. A flat layout works because one person holds the map in their head. With two people there are two maps, and they diverge immediately.

Type-based structure

What it is

Files are grouped by what they are: components together, hooks together, API calls together, stores together. React's legacy docs described this as grouping by file type and presented it as one of two common approaches without favoring either. For the reference app, that means Button, Modal, CartItem, CheckoutForm, and every other component in components/, all hooks in hooks/, all API modules in api/, and cartStore in store/.

Because most tutorials teach it, it is also the structure you will encounter most often in real projects.

Who uses it

Nearly every React tutorial, most bootcamp projects, and a large share of production apps that outgrew a flat layout without a plan. It is the easiest step to take: once src/ gets cluttered, a components/ folder is the natural first reaction.

Strengths

  • It needs no explanation. Nobody has ever had to be told what belongs in hooks/, so new developers can contribute on their first day using vocabulary they already know.
  • Classification is trivial. A component goes in components/, a hook goes in hooks/, and the file itself tells you which it is. Compare that with Atomic Design, where sensible people can debate a single component for an hour.
  • It is perfectly adequate at small scale. Below roughly 50 files it works well, and migrating away would cost more than it returns.

Weaknesses

  • It announces the framework instead of the product, which is precisely what Screaming Architecture objects to. Top-level folders named components/, hooks/, api/, and utils/ tell a newcomer you use React and nothing about what the app does. An online shop and a hospital scheduler look identical from the root.
  • Every change is spread across the tree. Adding one field to the cart means editing CartItem.jsx under components/, useCart.js under hooks/, cartApi.js under api/, and cartStore.js under store/. That is four directories and four scroll positions for a single logical change. Colocation is broken as policy: the files most likely to change in the same commit are the ones placed furthest from each other.
  • components/ turns into a new flat folder. In the reference app it ends up holding around 18 files with no internal grouping. The flat problem is solved for hooks and API modules, then reappears inside the folder that holds most of your code.
  • Removing a feature means searching several folders instead of one. Deleting checkout requires hunting through four directories and hoping nothing was missed.
  • Boundaries are still absent. Nothing stops ProductCard from importing useCheckout; the folders are purely cosmetic.

Where it breaks

Around 100 files, or once components/ passes about 20 entries. The warning sign is behavioral: developers start prefixing file names, such as CartItem, CartDrawer, and CartTotal, to simulate the grouping the folders do not provide. Those prefixes are the codebase asking for feature folders.

Feature-based structure

What it is

Files are grouped by what they do. Each feature owns its components, hooks, API calls, and state in one place, shared primitives live in a common area, and features are composed at the application level without importing one another.

React's legacy docs called this grouping by features or routes and offered a practical way to find the boundaries. Because the meaning of "feature" is not universal, you choose the granularity yourself, and when a list of top-level folders will not come to you, your users can describe the major parts of the product and use their mental model as the blueprint. That is arguably the most useful practical advice in this whole topic: your users already know where your feature boundaries are.

In effect, this is Screaming Architecture translated into React, and today it is the mainstream choice. In the reference app, features/cart/ holds the cart components, useCart, the cart API, and cartStore; features/checkout/ holds CheckoutForm, useCheckout, and checkoutApi; and so on, with Button, Modal, and the other primitives in a shared components/ folder.

Who uses it

The best-known reference implementation is Bulletproof React by Alan Alickovic, which had about 35.5k GitHub stars and was still actively maintained at the time of writing. It is the most widely copied React structure around, and the Bulletproof React project structure document is worth reading directly instead of relying on second-hand summaries.

Its recommended src/ consists of exactly eleven top-level folders, listed below with the purpose of each. Notice that only one of them, features, is organized by domain; the rest are shared layers that features draw on, plus the app layer that composes everything.

src
+-- app          # application layer (routes, app.tsx, provider.tsx, router.tsx)
+-- assets       # static files such as images, fonts, etc.
+-- components   # shared components used across the entire application
+-- config       # global configurations, exported env variables etc.
+-- features     # feature based modules
+-- hooks        # shared hooks used across the entire application
+-- lib          # reusable libraries preconfigured for the application
+-- stores       # global state stores
+-- testing      # test utilities and mocks
+-- types        # shared types used across the application
+-- utils        # shared utility functions

Inside each feature there can be up to seven segments: api, assets, components, hooks, stores, types, and utils. The documentation is explicit that a feature only needs the segments it actually uses.

Two rules do most of the real work:

  • No imports across features. Bulletproof React advises against one feature importing another and recommends composing features at the application level instead, so each stays independent and the codebase stays easier to follow.
  • One-way flow. Code should move in a single direction, from shared code to features to the app (shared -> features -> app).

Both rules can be enforced with the ESLint rule import/no-restricted-paths. That enforcement step is the part many teams skip, and it is usually why their boundaries erode over time.

Barrel files reversed: Bulletproof React previously recommended barrel files, an index.js that re-exports a feature's public surface. It now recommends importing files directly, because barrels can interfere with tree shaking in Vite and cause performance problems. If you learned the pattern from an older guide, the advice has flipped. Since the barrel was how most teams expressed a feature's public API, dropping it means the boundary now lives entirely in the lint configuration.

Strengths

  • Colocation comes by default. One cart change touches one folder, because files that change together live together.
  • Deleting a feature is a single rm -rf. Remove features/checkout/, fix the imports the compiler complains about, and you are done. This matters more than it sounds: code that is hard to delete tends never to be deleted.
  • The domain is visible from the root. A new engineer reading auth/, catalog/, cart/, checkout/, and orders/ understands the product before opening a single file.
  • Boundaries can be enforced as lint errors in CI instead of review comments.
  • It scales a long way, well beyond 1,000 files, since each new feature is a new folder that leaves existing ones alone.

Weaknesses

  • "Feature" has no fixed definition, so you will argue about it. CartTotal.jsx is rendered by both cart and checkout. Neither answer is correct; you simply make a decision and will probably revisit it.
  • Shared code needs a judgment call every time. useDebounce is only used by catalog search today. Make it global and the shared hooks folder fills with things used once. Keep it in features/catalog/hooks/ and, when billing needs it, you must duplicate it, import across features (which is banned), or promote it.
  • Splitting into features too early hurts even more than abstracting too early. Wrapping a 40-file app in five feature folders adds ritual, not structure.
  • The lint configuration does not scale on its own. Bulletproof React's cross-feature rule needs a separate zone per feature, each exempting itself. Ten features mean ten hand-written zones, and forgetting one silently leaves that feature unprotected.

Where it breaks

Mostly it does not, which is why it is the default. It becomes uncomfortable in two situations. First, when a single feature grows very large: a checkout/ folder with 80 files is just a type-based structure one level down. Second, when a real domain model emerges that does not line up with UI features. Those two pressures lead to the next three structures.

Atomic Design

What it is

Atomic Design is Brad Frost's methodology, introduced in a June 2013 blog post and expanded in a 2016 book. It defines five levels. Paraphrasing his definitions:

  • Atoms are the basic building blocks that every interface is made of.
  • Molecules are fairly simple groups of UI elements that work together as a unit.
  • Organisms are more complex components built from molecules, atoms, and even other organisms.
  • Templates are page-level structures that place components into a layout and describe the underlying content structure.
  • Pages are concrete instances of templates filled with real, representative content.

The detail almost everyone overlooks is Frost's own description of its purpose. He presents Atomic Design as a mental model rather than a linear process: a way to see an interface as both a coherent whole and a set of parts at the same time. It was never a folder structure. The community bolted the folders on afterwards.

Who uses it

Here the reality differs from the reputation. There is little verifiable evidence that major React design systems use a literal atoms/, molecules/, organisms/ folder taxonomy. Shopify Polaris and GitHub Primer, for instance, do not use that naming, despite search results suggesting otherwise. The clearly verifiable user is Pattern Lab, Frost's own tool built to demonstrate the method.

As a vocabulary among design system teams, Atomic Design is hugely influential. As a literal folder layout in production React applications, the adoption evidence is much thinner than its fame suggests. If someone claims a large company structures its React folders this way, ask to see the repository.

Strengths

  • It gives designers and engineers a shared language. This is the real benefit, and folders play no part in it. If a designer refers to an organism, an engineer immediately understands its rough scope, which is valuable on design system teams.
  • It encourages composition. The hierarchy pushes you to build small pieces and assemble them, which is good React practice wherever the files live.
  • It exposes your inventory of primitives. An atoms/ folder makes it obvious when you have three button variants that should be one.

Weaknesses

  • Classification is genuinely hard, and the creator acknowledges it. Modal.jsx combines a backdrop, a container, a header, and a close control. Is that a group of atoms and therefore a molecule, or a complex standalone piece and therefore an organism? Frost eventually published a rule of thumb: if breaking a component down yields basic elements such as images, headings, paragraphs, and buttons, it is a molecule; if it yields smaller components such as cards, share lists, or media objects, it is an organism. A heuristic like that exists only because the boundary is ambiguous, and every ambiguous boundary becomes a recurring review debate that delivers no user value.
  • It classifies only UI, so it cannot structure an application. This is the disqualifying flaw. Atomic Design has opinions about Button and CheckoutForm and none at all about useCheckout, checkoutApi, cartStore, or formatPrice. Half the files fall outside the taxonomy, so teams end up with atoms, molecules, and organisms plus a type-based layout for everything else, inheriting both sets of problems.
  • It works directly against colocation. Everything related to checkout ends up spread across organisms/, templates/, pages/, hooks/, and api/, permanently.
  • Components get promoted, so files move. A molecule grows a little, becomes an organism, moves to another folder, and breaks every import. The taxonomy depends on complexity, and complexity changes.
  • React's own legacy documentation cautioned against treating it as a strict rule rather than a helpful example.

Where it breaks

As soon as it leaves the design system. Ordering primitives by complexity inside packages/ui/ or shared/ui/ is reasonable. Stretched across an entire application, it collapses right away, since the bulk of any app consists of things other than UI components.

Frost's own stance supports this reading: he calls it a mental model rather than dogma and invites teams to rename the stages. Teams enforcing strict atoms and molecules across a React app are being more rigid than the person who invented the method.

Domain-driven structure

What it is

Domain-Driven Design comes from Eric Evans's 2003 book, written more than a decade before React existed, and it is an approach to modeling intricate business domains in code. Two of its ideas carry over to the frontend. A bounded context is an area of the software in which particular terms, definitions, and rules apply consistently. The ubiquitous language is the principle that code should use the same vocabulary as the business.

Applied to a React app, each domain becomes a top-level folder, split internally into layers:

  • domain/ holds entities and value objects: pure business rules.
  • application/ holds use cases.
  • infrastructure/ holds API clients and storage.
  • presentation/ holds the React components.

Dependencies point inward only, so the domain knows nothing about React or HTTP.

Watch what happens to the awkward files. formatPrice is no longer a helper; it turns into a Money value object that lives in catalog/domain/. useAuth shrinks to a thin presentation-layer wrapper over a loginUser use case. React is pushed deliberately to the edges.

Who uses it

The strongest practical reference is Alex Bespoyasov's Clean Architecture on Frontend, which comes with a complete React and TypeScript implementation in the bespoyasov/frontend-clean-architecture repository. His explanation of the layering is that the domain is the part that stays the same whether you switch from React to Angular or change a particular use case.

It is also worth knowing that Manfred Steyer, perhaps the most established advocate of frontend DDD, works primarily with Angular. Angular's class-based, service-oriented model with heavy dependency injection is a more natural home for DDD than React's functions and hooks. Before adopting a strong piece of frontend DDD guidance wholesale, confirm which framework it was written for.

This style appears in fintech, insurance, healthcare, logistics, and enterprise B2B software, where business rules are intricate and existed long before the UI.

Strengths

  • Business logic outlives the framework. A Cart entity with addItem rules is plain TypeScript; replacing React would leave the domain layer untouched. On a product expected to live for a decade, that is a compounding asset.
  • Rules become testable without React. There is nothing to render, no hooks, and no testing library, just functions and assertions that run in milliseconds. With complex rules, that alone can justify the structure.
  • The code mirrors the business conversation. When a domain expert says an order may not ship until its address is verified, that rule has exactly one home, a file named Order.ts.
  • Bounded contexts fit large organizations, since each context can belong to one team with an explicit interface to the others.

Weaknesses

  • The boilerplate is substantial, and it is the main cost. Each entity typically brings a domain model, a DTO, and a mapper between them: three files and a translation layer for what a feature-based app expresses as a single typed API response. Multiply that across twenty entities.
  • Most React apps lack a real domain. That is a judgment rather than a sourced fact, but it is the key point of this section. The typical React application renders data from an API owned by another team or service, and their real complexity lies in server state, caching, optimistic updates, and race conditions. DDD addresses none of those; a server-state library such as React Query solves the actual problem better than a domain layer. If your entities are typed API responses without invariants, the architecture costs money and protects nothing.
  • The frontend cannot really escape its framework. The promise is framework independence, but a frontend is always tied to a rendering model, routing, state management patterns, browser APIs, and build tooling. Clean architecture moves those constraints to the edges rather than removing them. That is a genuine benefit, just a smaller one than the pitch suggests.
  • UI bounded contexts are fuzzy. On the backend, data ownership gives you clear lines. A frontend shows whatever a screen needs, and one page often spans three contexts, so the boundaries become subjective and churn.
  • Onboarding takes time. A React developer who has never encountered DDD may need weeks to become productive, and someone will always ask why the mapper exists.

Where it breaks

When the domain exists only on paper. DDD is designed to pay off against real business complexity. If your invariants are real, such as money, contracts, regulated workflows, or rule-driven multi-step state machines, the investment returns. When the job is showing a product list from an endpoint, you have bought a great deal of ceremony to wrap fetch.

A useful test: can you list three business rules more interesting than a required-field check? If you cannot, what you have is a UI, not a domain.

Feature-Sliced Design

What it is

Feature-Sliced Design (FSD) is the only structure here that is a formal specification, complete with versioned releases, documentation, and an official architecture linter called steiger. The others are conventions; FSD is written down.

It has three levels of hierarchy:

  • Layers are the fixed top-level folders: app, pages, widgets, features, entities, and shared.
  • Slices are groups within a layer, divided by business meaning, that is, by what the code means to the product or the application.
  • Segments are groups within a slice, divided by technical purpose: ui, api, model, lib, and config.

The rule that holds it together is that a module in a slice may import other slices only if they sit on layers strictly below its own. Imports only ever point downward, and two slices sharing a layer may never import one another. Siblings have zero coupling, enforced by a linter rather than good intentions.

Two corrections to claims that circulate elsewhere are worth knowing:

  • The processes layer is deprecated. The documentation states that the current spec recommends avoiding it and moving its contents into features and app. Guides that still list seven layers including processes describe an older version.
  • The pages layer was not deprecated; it was elevated. Version 2.1, released in November 2024, carries the headline "Pages come first!" The guidance now is to keep more code inside pages: large UI blocks, forms, and data logic that no other page reuses should remain in that page's slice.

So in an FSD take on the reference app, CheckoutForm.tsx sits in pages/checkout/ui/ rather than in a feature slice. The v2.0 approach would have broken it into entities and features as a matter of principle, whereas v2.1 keeps it inside the page until another page actually needs part of it.

The motivation for the change is the interesting part. The entity-first decomposition of v2.0 produced weak cohesion: tracing one user flow meant walking through many folders, and dead code was hard to spot. The maintainers effectively admitted that the earlier version required too many architectural decisions before any code was written. The strongest criticism of FSD was therefore raised and addressed by its own authors. Credit is due for shipping the correction, but teams that adopted v2.0 paid for the lesson.

Who uses it

The adoption picture needs a candid look. At the time of writing, the FSD homepage has a section for companies using FSD that names none, and instead invites visitors to report their company.

The official examples page lists around 21 projects, mostly demos and hobby apps such as a RealWorld clone, a Sudoku game, a mock web store, and a small browser game. The entries that look like production software are small, including Roke.to, Moke Smoke, Moonlogs, and a Chrome extension.

The measurable signals, as of writing, are modest but real: about 297 GitHub repositories use the feature-sliced-design topic, the documentation repository has around 2.3k stars, and the linter around 388. The community is centered in the Russian-speaking developer world, which makes English-language adoption look thinner than it probably is.

No large, recognizable Western company could be confirmed as an FSD adopter from primary sources, and no official documentation names the creator of the methodology. For something this formalized, that is unusual and worth weighing in an adoption decision.

Strengths

  • It ends placement debates because the spec has already decided. Where does this file go? The spec answers. On a large team that saves real time, and it is where FSD clearly beats a feature-based layout, in which the definition of a feature keeps being reopened.
  • Boundaries are checked by machines. steiger and the ESLint configuration enforce the import rules, and enforced architecture decays far more slowly than unenforced architecture.
  • It scales to large teams with little coordination. Fixed layers and no sibling imports let teams work in parallel without colliding.
  • The split between entities and features clarifies a lot when you get it right. product is something that exists; add-to-cart is something a user does. They are different kinds of code, and most layouts blur them.

Weaknesses

  • The learning curve is steep, and FSD's own motivation page acknowledges the general problem, noting that existing approaches have a high entry threshold that makes hiring harder. FSD is not exempt: layers, slices, and segments are three taxonomies to learn before you can place a single file.
  • The v2.1 pivot signals churn. The primary unit of decomposition changed, and anyone standing on a spec has to follow its migrations.
  • Its own documentation calls it excessive for small projects, noting it is unnecessary for short-lived projects or when speed matters more than code quality.
  • Adoption evidence is thin. That is fine for a personal project and a harder sell when a stakeholder asks who else runs it in production.
  • Migrating a legacy codebase is expensive. Retrofitting layers and slices onto existing code is a large project with little visible payoff.

Where it breaks

On small teams and short-lived applications, where the ceremony outweighs the benefit. FSD targets big, durable products with many contributors who genuinely need enforced boundaries. For anything smaller, a feature-based layout delivers most of the benefit with a fraction of the concepts.

Modular structure and monorepos

What it is

Unlike the others, this is not about arranging src/, so it is covered last. It is a repository layout: several applications and shared packages in one repository, with a task runner coordinating them.

Turborepo's documentation describes the canonical shape: start by putting applications and services in apps/ and everything else, such as libraries and tooling, in packages/. See the Turborepo guide to laying out a repository.

Every app inside apps/ still needs one of the other six structures internally. A monorepo is orthogonal to how you organize a single application, and confusing the two is the most common misunderstanding in this topic.

At the time of writing, the main tools were turbo (2.10.x, roughly 73 million monthly downloads), nx (23.x, roughly 38 million), and lerna (9.x, roughly 7.5 million). Check current numbers before relying on them. Contrary to a widespread belief, Lerna is not deprecated. Nx took over its stewardship in 2022, when it risked becoming unmaintained. It continues to ship and now hands task running and caching to Nx while it concentrates on versioning and publishing.

Who uses it

Discussions of monorepos routinely cite Google, Meta, and Microsoft, which is misleading. Those are company-wide monorepos, not React monorepos, and they say nothing about your React app.

The numbers are still interesting. Google's repository was reported at roughly one billion files and 86TB (Potvin and Levenberg, Communications of the ACM, July 2016). Meta describes its Sapling-managed repository as containing tens of millions of files, commits, and branches. According to Brian Harry in May 2017, the Windows repository at Microsoft held about 3.5 million files totaling 300GB, worked on by roughly 4,000 engineers.

Each of those companies had to build its own tooling to cope: Google created Piper and Bazel, Meta built Sapling and EdenFS, and Microsoft wrote GVFS. Read that as a cost signal, not an endorsement. If a monorepo needs a dedicated infrastructure team to function, that tells you something about monorepos.

The relevant, verifiable React examples are different:

  • Vercel's next.js repository is a genuine React monorepo, with top-level apps/ and packages/, pnpm workspaces, and 19 packages, among them next, create-next-app, and eslint-config-next.
  • Shopify keeps its React Native apps in one monorepo covering Shop, Inbox, Point of Sale, Shopify Mobile, and Local Delivery. Its engineering team explained in September 2021 that a single monorepo lowers maintenance costs, because there is one process to improve and optimize instead of support for many separate packages and repositories.

That is the real case for a monorepo, and note that it concerns maintenance process, not code organization.

Strengths

  • Cross-package changes are atomic. Changing packages/ui/Button and all three consuming apps happens in one commit, one pull request, one review, and one CI run. With separate repositories, that becomes a version bump, a publish, and three dependency updates. This is the reason monorepos exist.
  • Sharing comes with real boundaries. packages/ui has its own package.json and an explicit public API, and a package boundary is the strongest kind JavaScript offers.
  • There is one version of everything: no app stuck on React 18 while another runs 19, one lockfile, one source of truth.
  • Large refactors become feasible. You can rename an API across every consumer in a single commit, with the compiler checking the result.

Weaknesses

  • Tooling is a real and permanent tax: workspaces, task graphs, remote caching, and CI matrices. Turborepo's own docs acknowledge the scaling issue, since every workspace brings its own test, lint, and build tasks and a single monorepo can end up with thousands of them. Turborepo exists to solve a problem that monorepos create.
  • CI gets slower and costlier before it gets faster. Without properly configured affected-change detection and caching, every pull request runs everything.
  • Onboarding is harder. Before a first commit, a new hire has to learn how workspaces resolve, how the task graph works, and how caching behaves.
  • Shared packages introduce coupling that masquerades as reuse. Once packages/ui has three consumers, every change risks regressions in three apps. Teams often respond by adding props and variants until the shared component becomes a configuration language nobody understands.

Where it breaks

When there is only one app. A monorepo whose apps/ folder holds a single application pays the full tooling tax for zero return: there is no code to share and no cross-app change to make atomically.

As for thresholds, no authoritative figure exists. Turborepo and Nx do not publish any team size or number of apps at which adoption becomes worthwhile. Precise figures that circulate online, such as a specific engineer headcount that supposedly calls for Turborepo, trace back to low-quality SEO content rather than vendor documentation or research. Anyone quoting an exact threshold is inventing it.

A reasonable rule of thumb, offered as opinion rather than fact: adopt a monorepo when you have two real applications that genuinely share code, or a single application alongside a design system that outside teams consume. Short of that, the costs exceed the gains, and your own situation may shift the line.

React Server Components do not dictate folders

Many recent articles claim that React Server Components change how you should structure folders, typically recommending separate components/server/ and components/client/ directories. Nothing in the official documentation supports that.

The use client directive marks a boundary in the module graph, not in the directory tree. The relevant Next.js rendering guide describes use client as declaring a boundary between the server and client module graphs, and explains that once a file carries the directive, its imports and the child components it renders directly all end up in the client bundle.

That second point is the crux. The boundary propagates through imports, not folders. A file inside a client/ directory is not a client component because of its location, and a file inside server/ gains no protection from that name. What decides the outcome is the directive plus the chain of imports.

If you want enforcement, the tools are the server-only and client-only npm packages, which make the build fail when code crosses the wrong boundary. Folders do not provide that.

Official guidance actually pushes in the opposite direction: move 'use client' down the tree, toward the leaf components, to keep the client bundle small. A top-level split into server and client folders encourages the reverse instinct.

Several App Router conventions do affect organization, and they are real:

  • Route groups, written as (folder), organize routes without changing the URL.
  • Private folders, written as _folder, opt out of routing entirely.
  • Colocation is safe by default, because a route only becomes publicly accessible once a page.js or route.js file is added. Components, tests, and utilities can sit directly inside route segments without becoming routes, so private folders are not required for that.

And Next.js states plainly that it is unopinionated about how you organize and colocate project files. Its documentation presents three organization strategies without ranking them.

How to choose

Here is the decision in compressed form, with the reasoning that matters most:

  • Fewer than about 30 files, or you are still discovering the problem: stay flat. Structure based on real change patterns beats structure guessed on day one, just as React's legacy docs suggested.
  • Under about 50 files, a small team, and a short lifespan: type-based is fine. It is unfashionable, and that does not matter; migrating would cost more than it saves.
  • Anything real and long-lived: go feature-based. It should be the default. It satisfies Screaming Architecture, honors colocation, makes deleting features trivial, and supports lint-enforced boundaries. Start from Bulletproof React's eleven folders, add import/no-restricted-paths zones early, and skip barrel files.
  • A large team, a long-lived product, and recurring arguments about what counts as a feature: consider FSD. You are buying a spec that has already made the decisions and a linter that enforces them, with thin adoption evidence and a high concept count as the price.
  • Genuinely complex business rules that outlast the UI: add a domain layer, not the full DDD apparatus. Move the rules into plain TypeScript entities that never import React and keep everything else feature-based. That captures most of the value for a fraction of the effort.
  • A design system with real consumers: use Atomic Design inside that package only, as vocabulary. Keep it out of the application's overall layout.
  • Several genuine apps that share genuine code: use a monorepo with apps/ and packages/, run by Turborepo or Nx, and let each app choose its own internal structure from this list.

One rule outranks all of these: the approaches combine. Real codebases mix them, and React's legacy docs said as much, noting that larger projects often use a blend of approaches in practice, so picking the "right" one at the start is not very important.

A perfectly reasonable production setup is a monorepo containing feature-based apps, a design system package organized along roughly atomic lines, and a domain layer in the one area with real business rules. Nobody is scoring your architecture for purity.

Key takeaways

The people closest to these conventions hold them loosely and keep revising them in public. Frost calls Atomic Design a mental model rather than dogma. React's docs capped the decision at five minutes and then removed the page. Next.js and Vite decline to take a position. FSD changed its primary unit of decomposition in v2.1, and Bulletproof React reversed its barrel-file advice. They changed course because they listened to real feedback rather than defending a position.

That leaves two durable rules, both older than every layout discussed here:

  • Screaming Architecture: top-level folders should tell a new engineer what the product does, not which framework it uses.
  • Colocation: things that change together should live as close together as is reasonable.

Every structure in this guide tries to satisfy those two rules at a particular scale. Choose the cheapest one that satisfies them for the application you have today, not the one you hope to have. The signals that it is time to move are specific: file name prefixes standing in for folders, four folders open for one change, or an hour lost debating whether something is a molecule. Act on those signals when they appear, and not before.