When you start a new project, the first real architectural decision is not which framework you pick—it is how you organize the codebase.
I have tried both camps. Tutorials almost always teach function-based folders (components/, hooks/, utils/). That is where most of us begin. But I became a fan of feature-based structure early, and I have not gone back for anything beyond a small prototype.
This post is my take on function-based vs feature-based organization—what each looks like, when each wins, and why I default to features.
The two approaches in one sentence
- Function-based: group files by what they are (components, hooks, utils).
- Feature-based: group files by what they belong to (signup, checkout, dashboard).
Same React app. Completely different mental model.
Function-based organization
In a function-based structure, directories are organized by the role each file plays. Components live with components. Hooks live with hooks. Utilities live with utilities.
A typical React src/ folder:
src/ ├── components/ ├── hooks/ ├── utils/ ├── services/ └── types/
Why people love it
- Easy to find “all components” or “all hooks” in one place.
- Encourages reuse—you see shared UI building blocks immediately.
- Clean and predictable when the codebase is small.
- Great for solo devs or tiny teams moving fast on one flow.
Where it breaks down
I have watched this pattern fall apart on real products:
- A
components/folder with 200 files and no context. - A “shared” hook that only checkout uses—but lives three folders away from checkout.
- Five developers editing the same giant directories and creating merge conflicts.
- Onboarding that starts with “search the whole repo and guess where things live.”
Function-based structure optimizes for finding similar file types. It does not optimize for shipping a feature end to end.
Feature-based organization
In a feature-based structure, top-level folders map to product areas or modules. Each feature owns its components, hooks, utils, and often its API calls and types.
src/ ├── signup/ │ ├── components/ │ ├── hooks/ │ └── utils/ ├── checkout/ │ ├── components/ │ ├── hooks/ │ └── utils/ ├── dashboard/ │ ├── components/ │ ├── hooks/ │ └── utils/ └── profile/ ├── components/ ├── hooks/ └── utils/
Open checkout/ and you have everything for checkout. That is the whole point.
Why I am a fan—and have been from the start
Feature-based folders match how product teams actually work:
- You think in features, not file types. “I am building profile settings” maps to one directory.
- Teams can work in parallel. Checkout and dashboard rarely step on each other.
- Boundaries stay obvious. Harder to accidentally couple unrelated domains.
- Onboarding gets faster. New devs learn one feature slice at a time instead of the entire tree.
- Scaling feels linear. Add a feature → add a folder. The structure grows with the product.
On client work and startup codebases, this saved me more time than any lint rule ever did.
The honest downsides
Feature-based is not free:
- You may duplicate small helpers across features (until you extract shared code deliberately).
- Reusable UI can be harder to discover if nobody maintains a
shared/orui/layer. - Too many tiny features → folder sprawl. Not every screen deserves its own top-level module.
Side-by-side comparison
| Function-based | Feature-based | |
|---|---|---|
| Best for | Small apps, solo devs, short timelines | Larger apps, multiple devs, long-lived products |
| Finding code | By file type | By product area |
| Team parallelism | Weak—everyone touches components/ | Strong—teams own feature folders |
| Reuse | Natural for generic UI | Needs a deliberate shared/ layer |
| Onboarding | Simple at first, confusing later | Slightly more setup, clearer long term |
| My default | Prototypes & demos | Production apps |
Function-based in practice (the good version)
Function-based is not wrong for a hackathon or a landing page. I still use it when:
- The app has one main flow and fewer than ~20 components.
- I am the only developer and will ship in a week.
- The goal is speed, not a two-year roadmap.
src/ ├── components/ ├── hooks/ └── pages/
Keep it flat. Do not over-engineer folder depth for a weekend project.
Feature-based in practice (how I structure Next.js apps)
For App Router projects, I align routes and colocated code with features:
src/ ├── app/ │ ├── (auth)/ │ │ ├── login/ │ │ └── signup/ │ └── (dashboard)/ │ ├── analytics/ │ └── settings/ ├── features/ │ ├── auth/ │ │ ├── components/ │ │ ├── hooks/ │ │ └── api/ │ ├── analytics/ │ └── settings/ └── shared/ ├── components/ui/ ├── lib/ └── types/
Routes handle URLs. features/ holds domain logic. shared/ is for code that is genuinely cross-cutting—not a dumping ground for “I did not know where to put this.”
That hybrid keeps feature boundaries without banning reusable primitives.
When I switch strategies
| Project stage | What I use |
|---|---|
| MVP / proof of concept | Function-based or minimal folders |
| First paying customers | Start splitting into features/ |
| Multiple engineers | Feature-based with explicit shared/ |
| Design system heavy | shared/components/ui + feature folders |
The mistake is picking function-based because a tutorial did—and never revisiting the decision when the team doubles.
The hybrid approach (what most mature codebases become)
Almost every large codebase ends up hybrid:
- Feature folders for product logic.
- Shared / ui / lib for primitives and cross-cutting utilities.
- App router segments that mirror feature names where it helps.
You do not have to be purist. You have to be intentional.
Conclusion
Function-based organization wins on day one: it is simple, familiar, and easy to grep.
Feature-based organization wins on day three hundred: when the app has real modules, real teams, and real merge conflicts.
I have been team feature-based since the beginning because I optimize for where the project is going, not just where it starts. If you are building something meant to last—with more than one developer or more than one major flow—organize by feature. Your future self (and your teammates) will thank you.
If you are still on function-based folders and the components/ directory is starting to feel like a junk drawer, that is usually the signal. Split one feature out, see how it feels, and grow from there.