跪拜 Guibai
← All articles
Frontend

Stop Treating `components` and `utils` Like Project Dumpsters

By Liora_Yvonne ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

A directory structure that groups by file type instead of business domain directly increases the cost of understanding and modifying a codebase. As a project grows, the `components` and `utils` folders become ungoverned dumping grounds where business rules hide inside files named like utilities, making refactors risky and onboarding slow.

Summary

Grouping front-end code by file type into `components`, `utils`, and `views` works for a project's first few weeks, then collapses. The structure tells you what a file is but not which business domain owns it, scattering related logic across a half-dozen directories and making changes a scavenger hunt.

The fix is to organize by the reason a file changes. Business-bound components and functions stay inside a `features/order` module. Pure, reusable UI and utilities sink to a `shared` layer. Application wiring lives in an `app` directory. This boundary answers three questions: what layer a file belongs to, why it will be modified, and what it's allowed to depend on.

A monorepo extends this same logic one level up. Shared infrastructure like a design system, request layer, or build config moves into `packages/` so no single application owns code that multiple apps consume. The directory structure is ultimately a dependency diagram: lower layers like `shared` must never import from upper layers like `features` or `app`.

Takeaways
Organize directories by the reason a file changes, not by its file type.
A business component like OrderStatus belongs in `features/order/components`, not in a global `components` folder.
Pure, framework-agnostic utility functions can live in `shared/utils`; business rules disguised as utilities must stay inside their domain module.
Page-local components should not be prematurely promoted to a shared directory; wait until a second or third consumer genuinely needs them.
Platform-specific wrappers for localStorage, clipboard, or routing deserve explicit directories like `shared/platform`, not a vague `utils` folder.
Service-layer helpers for token injection or error handling belong in `services/`, not `utils`.
In a monorepo, shared infrastructure like a UI kit or request layer moves to `packages/` so no single application owns cross-app code.
Dependency direction must flow one way: `app` and `features` depend on `services` and `shared`, never the reverse.
Use dependency injection to keep lower layers like `services` from importing application-layer constructs like `router`.
Delay abstraction until a stable pattern of reuse emerges; the first use stays local, the second is noted, and the third justifies extraction.
Conclusions

Calling a directory `utils` provides no boundary at all; the name is so vague it invites any function that doesn't have an obvious home, which is precisely how business logic leaks into supposedly generic code.

The common reflex to extract shared code early is a status display, not an engineering decision. It creates maintenance contracts before the team knows what stable reuse looks like.

A directory structure is a dependency diagram rendered as folders. When a lower layer imports from a higher one, the structure has already failed, regardless of how tidy it looks.

The six diagnostic questions offered are a lightweight, repeatable heuristic that replaces architectural dogma with a single test: what will force this file to change?

Concepts & terms
Dependency Direction
The rule that lower-level modules (shared utilities, services) must not import from higher-level modules (features, app). Violating this creates circular knowledge where stable code depends on volatile business logic.
Monorepo
A single repository containing multiple applications and shared packages. It allows cross-app infrastructure like a UI kit or request layer to be versioned and consumed from a central `packages/` directory rather than duplicated or awkwardly referenced.
Dependency Injection
A pattern where a module exposes configuration points rather than directly importing an implementation. The article shows a `configureServiceAuth` function that receives token retrieval and failure handlers, so the services layer never imports the router.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗