$ ls src/lib▊

Folders should name the business, not the framework

components/, services/, models/ and utils/ tell you which framework you are using, something you already knew. In an Nx Angular workspace they belong in exactly one kind of library: one whose users are developers, such as a UI kit. Everywhere else the top level of a folder should name what the business does. This article explains why, in the old language of cohesion, where the line sits, and what our git history said when we measured it.

Andrii Siuta · October 2026 · ~13 min read Angular Nx architecture
359domain changes measured
30 %touched 2+ type folders
13 %touched 3 or more

01. The five-second test

Open two feature libraries from the same product and give yourself five seconds with each:

library Asrc/lib/
  components/
  services/
  models/
  resolvers/
  utils/
  routes.ts
library Bsrc/lib/
  order-list/
  order-details/
  refund-request/
  cancel-order-dialog/
  orders.service.ts
  orders.routes.ts

Library A tells you that this is an Angular project. You knew that from package.json. It does not tell you what the library does, which screens it has, or where to start reading for the bug in the refund flow. Library B tells you all three before you open a file.

That is the whole argument. A folder name is the cheapest documentation a codebase has, and every reader sees it, whether a new developer, a reviewer or an AI agent. Spending it on components wastes it.

Every app has components. Only yours has refunds.

02. An old idea, now official

None of this is new. In 2011 Robert C. Martin asked what the top-level structure of a system "screams", and answered that it should be the use cases, not the framework: "Frameworks are tools to be used, not architectures to be conformed to."1 His Common Closure Principle gives the mechanical version: "Gather into components those classes that change for the same reasons and at the same times."2 Jimmy Bogard's vertical slices say it again from the other side: "Minimize coupling between slices, and maximize coupling in a slice."3 The Java community called it package by feature, not layer and gave it its best test: "an item has maximum modularity only if it can be deleted in a single operation."4

What is new is that the Angular and Nx docs now say the same thing outright. The current Angular style guide:

"Avoid creating subdirectories based on the type of code that lives in those directories. For example, avoid creating directories like components, directives, and services."5

The same guide asks you to avoid generic file names such as helpers.ts, utils.ts and common.ts, and to keep tests beside the code they test.5 Nx says it one level up, about libraries: "Group libraries by the application or business domain they serve, not by their technical type. Projects that change together should sit together."6

The older Angular guide recommended folders by feature but still showed a shared/ full of components/ and services/.7 The rewrite drops that hedge. If your workspace was generated before it, the type folders you have came from the old advice, not from a decision anyone made.

03. Cohesion: together for what reason?

"Keep related things together" is not enough of a rule, because things can be related in many ways at once. The question that matters is why they are together. In 1974 Stevens, Myers and Constantine ranked the reasons from worst to best.13, 14 The scale is fifty years old and reads like a review of a modern Angular repository:

CohesionGrouped because…What it looks like in Angular
coincidental (worst)no reason; it had to go somewhereutils/, helpers.ts, common/
logicalthey are the same kind of thing, "different by nature"components/, services/, pipes/, models/
temporalthey run at the same timean app-initialiser file doing five unrelated start-up jobs
proceduralthey run in a fixed ordera steps/ folder of a wizard, sorted by step number
communicationalthey work on the same dataa data-access library around one entity: its model, API class and store
sequentialone part's output is the next part's inputan import flow: parse → map → validate
functional (best)they all serve one well-defined taska screen slice: refund-request/ with its component, resolver, service and model

A type folder is logical cohesion by definition. The order list component and the invoice chart component sit together in components/ because they share a decorator, not because they share a reason to change. That is the second-worst rung, one step above "it had to go somewhere", and utils/ usually is that bottom rung. That is also why the Angular guide warns against a file called utils.ts.

The real problem is that every file has several cohesions at once. The refund component is logically related to every other component, communicationally related to the order model, and functionally related to the refund resolver and the refund service. A folder can express only one of them. Sorting by type picks the weakest and scatters the strongest across siblings. Section 04 measures exactly that scattering.

the rule, in cohesion terms

Group by the strongest cohesion the reader needs. In a feature, that is functional cohesion: the screen or use case. In a data layer, it is communicational: the entity. Logical cohesion is acceptable only where its parts never change together anyway, which is what makes a UI kit different (section 06).

04. What our git history said

"Things that change together live together" can be measured, and I could not find an Angular write-up that had done it. So before we re-sliced our feature libraries, I measured it on our own repository.

The codebase at the time was a single Angular app with a dozen business domains, each a folder organised by kind: components/, services/, models/, resolvers/, store/, utils/ and so on. I took every non-merge commit from six and a half months of history and, for each domain a commit touched, counted how many of that domain's type folders it changed.

Type folders touched in one domain, per commitDomain changesShare
125170 %
2 or more10830 %
3 or more4713 %

Almost a third of the time, one change to one business capability was spread across sibling folders that existed only to sort files by Angular concept. The typical shape: a new field on a screen touches the model, the service that loads it, the resolver, and the component that shows it, in four folders. In the review diff those four files sit far apart and look unrelated.

The 70 % touching only one folder is not the good news it looks like. Most of those are one-file fixes, and a one-file change cannot be scattered whatever the layout. The question that matters is what happens to the changes that span files. Those are the feature work, and that is where the scattering lived.

try it on your repo

List the files of each commit, map each path to (domain, first folder under the domain), and count the distinct type folders per domain per commit. It takes about twenty lines of script. If the share at 2+ is high, your folders are sorted along an axis your changes do not follow.

05. One slice, four boundaries

In an Nx Angular workspace a business slice is not just tidier. It lines up with four boundaries the toolchain already draws, and a type folder lines up with none of them.

One slice, four boundaries On the left a business slice: its route, lazy chunk, route injector and Nx project all coincide. On the right a services folder: it spans every route, ships in every chunk, belongs to no injector and is not a project. business slice: order-details/ route/orders/:id lazy chunkloaded on visit DI scoperoute providers nx affectedthe feature lib type folder: services/ routeevery route lazy chunksplit across all DI scopenone of its own nx affectednot a unit left: the four boundaries coincide · right: the folder cuts across all four
A screen slice is a route, a lazy chunk, a DI scope and (at library level) a unit of nx affected. A services/ folder is none of these, so the tools cannot use it.
  • Route. A slice is what a URL points to. The route file lists the slices; the folder list and the route list read the same.
  • Lazy chunk. A feature library is lazy-loaded. What sits in it ships when the user visits it. Code sorted into shared/services/ ships wherever any importer goes.
  • DI scope. A domain's providers array sits on its root route. Services that belong to a slice live beside it and die with it. Nobody needs to ask whether a service in services/ is still used.
  • The unit of nx affected. Nx re-tests and re-builds projects, not folders. Libraries cut along business lines keep the affected set small. Nx says it directly: "when organizing projects you should think about your business domains", and the finer the projects, the more effective affected and the cache become.8

06. When type folders are right

Type folders are not always wrong. Look at how real component libraries lay out their source. Angular Material, PrimeNG and Spartan have one folder per component (button/, dialog/, table/).9 Taiga UI goes further and uses literal type folders: components/, directives/, pipes/, tokens/, utils/. RxJS keeps operators/, observable/ and scheduler/.9 These are some of the best-maintained libraries in the ecosystem. They are not breaking the rule. They are following it.

The test that explains it:

Name the folders in the vocabulary of the library's users. When the user is a developer, the developer's vocabulary is the domain.

The users of a UI kit are developers looking for "the dialog" or "a pipe". For them dialog/ is a business capability, and directives/ is how they search. The users of an order-management screen are operations staff who think in orders, refunds and cancellations. Nobody in that building has ever asked for "a service".

Martin's component principles give the deeper reason. A library many teams consume is grouped for reuse: things used together ship together. An application is grouped for change: things that change together live together.2 A kit sits on the reuse side, a feature on the change side. Type folders are a reuse grouping, so they fit the kit and fight the feature.

In cohesion terms, each component folder of a kit (button/, dialog/) is functionally cohesive: everything for one component, together. Taiga's components/ then groups those units logically one level up. That costs nothing, because the button and the dialog never change together, and it gives developers a catalogue. Logical cohesion only hurts when it splits parts that do change together, which is the normal case in a feature.

LibraryIts usersTop-level folders
UI kit / design systemdevelopersper component, or by kind (components/ directives/ pipes/): fine
utildevelopersby kind or by topic: fine (but no file called utils.ts)
data-accessother codemodels/ services/ store/: fine, because the type is the axis of change in a data layer
featureend usersslices named for screens and use cases, never type folders
the appeveryoneno code folders at all; bootstrap and config only

07. The two-level rule in Nx

Nx makes this rule easy to state because it already has two levels of name. Neither the Angular guide nor the Nx docs spell it out, so here it is.

  1. Level one, the library: always business first. libs/<domain>/<type>. The scope folder is a domain such as orders, catalog or billing. The type (feature, ui, data-access, util) is the second word, and the boundary lint reads it from a tag. Even a technical layer is filed under its business owner.
  2. Level two, inside the library: it depends on who the users are. A feature library is sliced by screen. A ui, util or data-access library may sort by kind, because its users are developers or other code.
both levels at oncelibs/
  orders/                      level 1: the business
    feature/src/lib/
      order-list/              level 2: screens, for end users
      refund-request/
      orders.routes.ts
    data-access/src/lib/
      models/                  level 2: kinds, for other code
      services/
      store/
  ui-kit/
    core/src/lib/
      button/                  level 2: components, for developers
      dialog/

Note what the first level does to shared. A shared/components library is a type folder promoted to a project, with all the problems above at a larger scale. Keep shared for what genuinely has no business owner (the HTTP layer, the design-system wrappers) and send everything else home to its domain.

08. The best objection, answered

The strongest case for type folders comes from Josh Comeau: deciding which feature a file belongs to is genuinely hard, the categories drift as the product changes, and sorting by type never needs a decision.10 He is right that slicing takes judgement. In an Nx Angular workspace the costs he worries about are mostly gone:

  • Moving is cheap. Renaming a slice is a folder move and a relative-import fix. Moving a library is a generator run, and Nx's own docs answer "can you change the folder structure later?" with a plain yes.6 Getting a slice name wrong costs one small PR.
  • The routes file settles most of it. A screen is a slice. A file used by two screens goes up to the library root; a file used by two domains goes down into ui or data-access. That is three rules, not a judgement call per file.
  • The lint catches the expensive mistakes. A slice placed in the wrong domain shows up as a boundary violation the day it imports something it should not.
  • Type folders drift too, just silently. A utils/ folder never looks wrong; it just grows. A misplaced business slice looks wrong, which is exactly why it gets fixed.

09. Agents read folders too

A newer reason: AI coding agents navigate a repository mostly by listing folders and searching for names, and every file they open costs context. Anthropic's context-engineering guide lists folder hierarchies and naming conventions among the signals that help "both humans and agents".11 A 2026 preprint measured agents taking a third fewer navigation steps when they were given architecture context, though it measured written descriptions, not folder layouts.12 The evidence that folder layout itself matters is still thin, so I will only describe what we see.

When the task is "add a field to the refund screen", a sliced library hands the agent the four files it needs in one directory listing. A type-sorted one makes it search four folders and guess which of a dozen services is the right one. Our agents get the same "where does it go" table our developers do, and a misplaced file is the same check failure for both.

10. Making it stick

A folder convention without a check lasts about one busy sprint. Ours is held by a small spec that runs with the unit tests and has one row per library:

  • It reads the library's type: tag from its project file.
  • For a feature library, it fails if the top level of src/lib contains a technical name (components, services, models, utils, pipes, directives and the rest) or a Feature-Sliced Design segment name.
  • For a data-access library, it allows only a closed list of kind folders.
  • For ui and util, it allows kind folders, so a kit keeps its natural shape.
  • It fails if a file at a feature library's root is used by only one slice, because that file belongs in the slice.

It is under two hundred lines and runs in about a second. When we re-sliced the feature libraries it took 852 file renames across 13 libraries, and the spec has kept the old shape from creeping back since.

If you take one thing from this

  • Ask why the files are together. A type folder is logical cohesion; a screen slice is functional cohesion. Pick the strongest.
  • Ask who the library's users are. End users mean business slices; developers mean type folders are fine.
  • Name level one (the library) for the business, always. Choose level two by the users.
  • Measure co-change on your own history before arguing about taste.
  • Back the rule with a spec keyed on the Nx type: tag, so it holds for people and agents alike.

Your folders are the first page of your documentation. Write it about your business.

How the workspace these rules come from is laid out end to end is in Where does this file go?, and how we got there is the Nx migration case study.

11. Sources

  1. Robert C. Martin, "Screaming Architecture", 2011. blog.cleancoder.com
  2. Robert C. Martin, Clean Architecture, ch. 13 "Component Cohesion", 2017. O'Reilly
  3. Jimmy Bogard, "Vertical Slice Architecture", 2018. jimmybogard.com
  4. "Package by feature, not layer". javapractices.com
  5. Angular style guide. angular.dev/style-guide
  6. Nx, "Folder Structure". nx.dev
  7. Angular style guide, v17 (folders-by-feature, LIFT). v17.angular.io
  8. Nx, "Project Size". nx.dev
  9. Source trees: Angular Material, PrimeNG, Spartan, Taiga UI, RxJS 7.
  10. Josh W. Comeau, "Delightful React File/Directory Structure". joshwcomeau.com
  11. Anthropic, "Effective context engineering for AI agents", 2025. anthropic.com
  12. arXiv 2604.13108, 2026 (preprint). arxiv.org
  13. W. P. Stevens, G. J. Myers, L. L. Constantine, "Structured Design", IBM Systems Journal 13(2), 1974. doi.org/10.1147/sj.132.0115
  14. E. Yourdon, L. L. Constantine, Structured Design: Fundamentals of a Discipline of Computer Program and Systems Design, 1979. Summary of the cohesion scale: Wikipedia, "Cohesion (computer science)"

Further reading: Kent C. Dodds, "Colocation"; Manfred Steyer, strategic design with Sheriff and standalone components; Nx, project dependency rules.