React component architecture thrives on colocation. Learn why grouping logic, state, and UI by feature beats file-type organization for scalable Next.js apps.
I remember the exact moment I realized my project structure was a disaster. I was working on a dashboard feature in a medium-sized Next.js app, and I spent about 15 minutes just jumping between four different folders—components/, hooks/, styles/, and utils/—just to change the color of a button and the validation logic for its parent form.
When you’re starting out, following the "standard" folder-by-type structure feels organized. But as your application grows to include 50+ components, that structure becomes a tax on your brain. You aren't coding; you're playing "find the file."
The core problem with grouping by file type is that it separates things that change together. If you’re modifying a feature, you’re almost always touching the UI, the state logic, and the styles simultaneously.
When you embrace colocation, you group files based on the feature they serve. Instead of a massive hooks/ folder, you have a features/user-profile/ directory containing the component, its specific hooks, its styles, and even its local tests.
This shift isn't just about tidiness. It’s about cognitive load. When I open a feature folder, I want to see everything that makes that feature tick in one place. If you're struggling with state flow, you might want to review React state management: Mapping Your Next.js Component Hierarchy to see how this physical layout mirrors your data flow.
Early in my career, I tried to make everything "reusable." I’d take a button, a label, and a fetch hook and move them into a global shared/ folder because "I might need this in another page."
It broke my flow. Every time I needed to tweak the button, I had to worry about breaking five other pages. I spent more time maintaining the "shared" abstraction than building the product.
Now, I follow the "Rule of Three":
When you're building a modern app, your Next.js folder structure should prioritize discoverability. I usually organize my src/ directory like this:
TEXTsrc/ app/ (dashboard)/ page.tsx layout.tsx features/ user-settings/ components/ SettingsForm.tsx AvatarUploader.tsx hooks/ useSettings.ts user-settings.test.ts index.ts
By exporting only what you need from the index.ts file in the feature folder, you keep your imports clean. You don't have to worry about the internal implementation details of the user-settings module from your app/ directory.
One common concern juniors have is: "If I colocate, where does my global state go?"
The answer is: keep it as close to where it’s consumed as possible. If you’re using React Context or simple useState, don't feel obligated to put it in a global store/ directory. If it’s only used by the user-settings feature, it lives inside that feature's hooks/ folder.
If your state logic is getting messy, it’s usually not because of the folder structure, but because the logic is too complex for a simple hook. That’s when you might look at TypeScript State Machines: Building Predictable UI Logic with XState to formalize your transitions.
Is colocation perfect? No. You’ll eventually have folders that feel slightly bloated. Sometimes, a feature grows so large that it deserves to be its own micro-app or package.
However, the alternative—a components/ folder with 200 files—is almost always worse. When files are scattered, you lose track of the "why" behind the code. You see a useFetch.ts hook but have no idea which component it was designed for, leading to weird bugs during re-renders. If you're running into those issues, check out React rendering: Mastering State Batching and the Two-Pass Model to understand how your local state lifecycle works.
Q: Does colocation make it harder to find shared components?
A: Not if you use an index.ts file to manage your public API. Treat your feature folders like internal modules. If you need something globally, move it to a components/ui/ folder, but only after you’ve verified it’s truly generic.
Q: Should I put my unit tests inside the feature folder?
A: Absolutely. Having the test right next to the component makes it 10x more likely that you’ll actually run the test and keep it updated. If the test is in a separate __tests__ directory at the root, it’s out of sight and out of mind.
Q: Is this overkill for small projects? A: Maybe. For a landing page, a flat structure is fine. But I’ve found that even in small projects, starting with a feature-based structure prevents the "refactor-nightmare" that happens when a project suddenly doubles in size.
I'm still experimenting with how to handle shared styles across different features. Sometimes I use CSS modules, sometimes Tailwind, and the boundaries can get blurry. Don't stress about being perfect. Just focus on keeping the code that belongs together, together. Your future self will thank you when you don't have to hunt through five directories to fix one line of logic.
Next.js Streaming SSR and progressive payload serialization can drastically reduce latency. Learn how to optimize data graphs for faster, smoother delivery.