Collocation on the Frontend Web: Why It Matters and How I Do It in Next.js (or any frontend framework at all)


"Put things where they’re used." That’s the golden rule of collocation in frontend development, and once I embraced it fully, my Next.js projects became easier to scale, debug, and maintain.
TLDR;
Collocation makes it easier to find everything related to a feature, component or page in your code, whether you want to make an update or remove an entire feature. Your project’s features are more modular and reusable, and onboarding a new developer to your codebase becomes less stressful. 😉
What is Collocation?
Collocation in frontend web development simply means placing related files and logic close to each other, or in the same folder or context where they are actually used.
So instead of scattering styles in one folder, components in another, and tests in another, you keep everything related to a single feature or component tightly grouped.
Think of it like packing for a trip. You don’t keep your toothbrush in your garage and your passport in the kitchen. You group things by purpose. It’s the same idea here, just that we’re now “packing” our code ☺️.
Why Collocation Matters on the Frontend
In a modern frontend stack, especially with frameworks like Next.js, collocation is not just a pattern. It’s a best practice. Why?
Discoverability: Easier to find everything related to a component or page.
Cognitive simplicity: No need to mentally map files across the entire tree.
Testing is easier: Test files sit next to the components they test.
Scoped styles and logic: CSS Modules, hooks, and utils don’t leak across the app.
Encapsulation: Your features are modular and reusable.
A Typical Next.js Project Without Collocation
Here’s a simple (and painful) structure I’ve seen too many times:
/components
Navbar.tsx
Button.tsx
/styles
Navbar.module.css
Button.module.css
/pages
index.tsx
about.tsx
/tests
Navbar.test.tsx
Button.test.tsx
Now imagine maintaining this at scale. 🥲
With Collocation
Here’s how I typically structure my Next.js apps now using collocation:
my-next-app/
├── src/
│ ├── app/ # Top-level route components
│ │ └── login/
│ │ ├── layout.tsx
│ │ └── page.tsx
│ │ └── assets/ # Global static assets (images, fonts)
│ │ ├── logo.svg
│ │ └── favicon.ico
│ │ ├── layout.tsx
│ │ ├── loading.tsx
│ │ └── page.tsx
│ ├── components/ # Shared components
│ │ └── Button/
│ │ ├── Button.tsx
│ │ ├── Button.module.css
│ │ └── Button.test.tsx
│ ├── features/ # Feature-specific modules
│ │ └── auth/
│ │ ├── components/
│ │ │ ├── Login.tsx
│ │ │ ├── Login.module.css
│ │ │ └── Login.test.tsx
│ │ ├── hooks/
│ │ │ └── useAuth.ts
│ │ ├── services/
│ │ │ └── authService.ts
│ │ └── types.ts
│ ├── hooks/ # Global reusable hooks
│ │ └── useMediaQuery.ts
│ ├── layouts/ # Layout components (e.g., MainLayout)
│ │ └── MainLayout.tsx
│ ├── types/ # Global type declarations
│ │ └── index.d.ts
│ ├── utils/ # Utility functions
│ │ └── formatDate.ts
│ └── middleware.ts # Middleware component
├── .gitignore
├── package.json
├── tsconfig.json
├── next-env.d.ts
└── next.config.mjs
Let’s break it down:
Each route (like
/login
) has its own folder with its page component and supporting components in the features folder. Now, this is because I prefer not to have my component and logic files in the app folder. You can also decide to move you feature-specific folder to the feature/route folder in the app directory if you prefer them that close.Like this;
/app/login/ page.tsx layout.tsx Login.tsx Login.module.css Login.test.tsx useAuth.ts
Styles, tests, and local hooks sit right next to the components they belong to.
Shared UI components or layout wrappers go into
components
folder.Shared utils go in
utils
.
It’s clean, it’s local and, it scales!
Bonus: A Vite App With Collocation
my-vite-app/
├── public/ # Static assets
│ └── favicon.svg
├── src/
│ ├── assets/ # Global static assets (images, fonts)
│ │ └── logo.svg
│ ├── components/ # Shared components
│ │ └── Button/
│ │ ├── Button.tsx
│ │ ├── Button.module.css
│ │ └── Button.test.tsx
│ ├── features/ # Feature-specific modules
│ │ └── auth/
│ │ ├── components/
│ │ │ └── LoginForm.tsx
│ │ ├── hooks/
│ │ │ └── useAuth.ts
│ │ ├── services/
│ │ │ └── authService.ts
│ │ └── types.ts
│ ├── hooks/ # Global reusable hooks
│ │ └── useMediaQuery.ts
│ ├── layouts/ # Layout components (e.g., MainLayout)
│ │ └── MainLayout.tsx
│ ├── pages/ # Top-level route components
│ │ └── Home/
│ │ ├── Home.tsx
│ │ └── Home.module.css
│ ├── routes/
│ │ └── index.tsx
│ ├── types/ # Global type declarations
│ │ └── index.d.ts
│ ├── utils/ # Utility functions
│ │ └── formatDate.ts
│ ├── App.tsx # Root component
│ ├── main.tsx # Vite entry point
│ └── vite-env.d.ts
├── .gitignore
├── index.html
├── package.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
A Collocation Example in Code
Say I have a Hero
section in my homepage. Here’s what that folder might look like:
Hero.tsx
import styles from './hero.module.css';
import { useHeroData } from './useHeroData';
export default function Hero() {
const { title, subtitle } = useHeroData();
return (
<section className={styles.hero}>
<h1>{title}</h1>
<p>{subtitle}</p>
</section>
);
}
useHeroData.ts
export function useHeroData() {
return {
title: 'Welcome to My Site',
subtitle: 'Frontend, Fast, and Beautiful',
};
}
Everything lives in one place. If I ever need to update or remove the Hero
, I know exactly what to touch.
Also, if I need to remove an entire feature, I know where to go and not leave any unused code behind.
Pro Tip: Collocation ≠ Coupling
A lot of people confuse collocation with tight coupling. Nope.
You can still keep components modular and testable while collocating. You're just being smarter at putting tools next to the task.
PS: Coupling speaks to how much one component or module depends on another in a project. For your components to be tightly coupled means that you have components that are heavily connected or dependent on one another and changing one often breaks the other.
I’ll be writing more about this later! 🙃
Summary
Collocation is like flossing; everyone agrees it’s good for you, but only a few people do it consistently. Once I made it a rule in my Next.js workflow, things got really smoother.
🥂 Fewer bugs.
👌 Fewer mental jumps.
💪 Better developer experience.
If you’re starting a new project, collocate from day one. If you're refactoring an old one, do it incrementally and I promise you won’t regret it!
Subscribe to my newsletter
Read articles from Gospel Chinyereugo directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
