Skip to Content
chalvien 1.0 is released
DocumentationGuidesNextjsCollocation

Introduction

In Next.js, a colocation-first approach means placing related files — such as components, styles, tests, and data-fetching logic — close to the routes or features that use them.

With this structure, UI, logic, and utilities are organized within the route segments where they are primarily needed, while the global directories remain minimal and contain only elements that are truly shared across the application.

Pros

  • Reduced Cognitive Overhead: Developers can find all logic for a specific feature in one place rather than jumping between global folders.
  • Easier Maintenance & Refactoring: Deleting a feature or route becomes trivial because most of its dependencies are contained within its own folder.
  • Scalability: Prevents a global components/ or utils/ folder from becoming a “chaotic mess” as the project grows to dozens of routes.
  • Implicit Privacy: In the Next.js App Router, files like button.tsx or data-fetcher.ts placed inside route folders are not publicly addressable unless they are specifically named page.js or route.js.

Cons

  • Duplication Risk: Without discipline, developers might recreate similar utilities or components in different route folders instead of sharing them.
  • Learning Curve: Beginners may find it harder to navigate compared to traditional “folder-by-type” (e.g., all components in one folder) structures.
  • Utility Drift: Global utility providers or shared libraries can sometimes be “left behind” even after the features using them are removed.

Best Practices

  • Use Route Groups: Wrap related routes in folders with parentheses, like (dashboard)/ or (auth)/, to organize files without affecting the URL path.
  • Private Folders: Prefix folders with an underscore (e.g., _components/) to explicitly mark them as private implementation details and opt them out of routing.
  • The “One-Level” Rule: Avoid deep nesting; generally, one level of nesting within a route folder is sufficient for local components and styles.
  • Hybrid Approach for Shared UI: Keep route-specific logic local, but move truly shared primitives (like buttons, inputs, or modals) to a top-level components/ui/ folder.
  • Define Clear Boundaries: Use Module Path Aliases (e.g., @/components/*) to keep imports clean even when files move within a nested structure

Suggested Folder Structure

src/ ├── app/ # Next.js App Router (Routing & Features) │ ├── (auth)/ # Route Group: Org only, omitted from URL │ │ ├── login/ │ │ │ ├── _components/ # Private: Login-only UI (e.g., LoginForm.tsx) │ │ │ ├── _lib/ # Private: Login-only utils or actions │ │ │ ├── layout.tsx # Specific layout for login │ │ │ └── page.tsx # Public: /login │ │ └── register/ │ │ ├── page.tsx # Public: /register │ ├── (dashboard)/ # Route Group for internal app area │ │ ├── layout.tsx # Shared sidebar/nav for dashboard │ │ ├── overview/ │ │ │ ├── _components/ # Charts/Stats specific to overview │ │ │ └── page.tsx # Public: /overview │ │ └── settings/ │ │ └── page.tsx # Public: /settings │ ├── layout.tsx # Global Root Layout │ └── page.tsx # Public: Homepage (/) ├── components/ # Global Shared UI (The "Design System") │ ├── ui/ # Primitive components (Button, Input, Badge) │ └── common/ # High-level shared UI (Navbar, Footer) ├── hooks/ # Global React Hooks ├── lib/ # Global Shared Utils (Prisma client, etc.) └── types/ # Global TypeScript interfaces

Below is a clean, idempotent script that creates the folders and optional placeholder files.

#!/bin/bash # Base directory BASE="src" echo "Creating Next.js colocation-first structure..." # App Router structure mkdir -p $BASE/app/"(auth)"/login/_components mkdir -p $BASE/app/"(auth)"/login/_lib mkdir -p $BASE/app/"(auth)"/register mkdir -p $BASE/app/"(dashboard)"/overview/_components mkdir -p $BASE/app/"(dashboard)"/settings # Global directories mkdir -p $BASE/components/ui mkdir -p $BASE/components/common mkdir -p $BASE/hooks mkdir -p $BASE/lib mkdir -p $BASE/types # Create placeholder files touch $BASE/app/layout.tsx touch $BASE/app/page.tsx touch $BASE/app/"(auth)"/login/layout.tsx touch $BASE/app/"(auth)"/login/page.tsx touch $BASE/app/"(auth)"/register/page.tsx touch $BASE/app/"(dashboard)"/layout.tsx touch $BASE/app/"(dashboard)"/overview/page.tsx touch $BASE/app/"(dashboard)"/settings/page.tsx echo "Folder structure created successfully."

How to use

  • 1.Save as:
setup-next-structure.sh
    1. Make executable:
chmod +x setup-next-structure.sh
    1. Run inside your project root:
./setup-next-structure.sh

Small improvement (useful in real projects)

Many developers also add:

_components _hooks _lib _types

inside feature folders when needed, keeping everything fully colocated, which works very well with Next.js App Router.

Repeatable Next.js Starter Template

Below is a more complete bootstrap script that creates:

  • the colocation-first folder structure
  • basic starter files
  • TypeScript path aliases (@/components, @/lib, etc.)
  • minimal Next.js layout and page templates

It is designed for projects built with Next.js using the App Router.

1. Bootstrap Script

Save as:

bootstrap-next-structure.sh
#!/bin/bash echo "Bootstrapping Next.js project structure..." BASE="src" # ------------------------- # Folder structure # ------------------------- mkdir -p $BASE/app/"(auth)"/login/_components mkdir -p $BASE/app/"(auth)"/login/_lib mkdir -p $BASE/app/"(auth)"/register mkdir -p $BASE/app/"(dashboard)"/overview/_components mkdir -p $BASE/app/"(dashboard)"/settings mkdir -p $BASE/components/ui mkdir -p $BASE/components/common mkdir -p $BASE/hooks mkdir -p $BASE/lib mkdir -p $BASE/types # ------------------------- # Root layout # ------------------------- cat <<EOF > $BASE/app/layout.tsx export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body> {children} </body> </html> ) } EOF # ------------------------- # Homepage # ------------------------- cat <<EOF > $BASE/app/page.tsx export default function HomePage() { return ( <main> <h1>Next.js App</h1> <p>Welcome to your application.</p> </main> ) } EOF # ------------------------- # Auth pages # ------------------------- cat <<EOF > $BASE/app/"(auth)"/login/layout.tsx export default function LoginLayout({ children, }: { children: React.ReactNode }) { return ( <div> <h2>Authentication</h2> {children} </div> ) } EOF cat <<EOF > $BASE/app/"(auth)"/login/page.tsx export default function LoginPage() { return <div>Login Page</div> } EOF cat <<EOF > $BASE/app/"(auth)"/register/page.tsx export default function RegisterPage() { return <div>Register Page</div> } EOF # ------------------------- # Dashboard # ------------------------- cat <<EOF > $BASE/app/"(dashboard)"/layout.tsx export default function DashboardLayout({ children, }: { children: React.ReactNode }) { return ( <div> <aside>Sidebar</aside> <main>{children}</main> </div> ) } EOF cat <<EOF > $BASE/app/"(dashboard)"/overview/page.tsx export default function OverviewPage() { return <div>Dashboard Overview</div> } EOF cat <<EOF > $BASE/app/"(dashboard)"/settings/page.tsx export default function SettingsPage() { return <div>Settings</div> } EOF # ------------------------- # tsconfig aliases # ------------------------- cat <<EOF > tsconfig.paths.json { "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"], "@/components/*": ["src/components/*"], "@/hooks/*": ["src/hooks/*"], "@/lib/*": ["src/lib/*"], "@/types/*": ["src/types/*"] } } } EOF echo "Structure created successfully." echo "Remember to merge tsconfig.paths.json into your tsconfig.json."

2. Add Path Aliases to tsconfig.json

Update your tsconfig.json:

{ "extends": "./tsconfig.paths.json" }

Now you can import like:

import { Button } from "@/components/ui/button" import { prisma } from "@/lib/prisma"

3. Resulting Structure

src ├── app │ ├── (auth) │ │ ├── login │ │ │ ├── _components │ │ │ ├── _lib │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ └── register │ │ └── page.tsx │ ├── (dashboard) │ │ ├── layout.tsx │ │ ├── overview │ │ │ ├── _components │ │ │ └── page.tsx │ │ └── settings │ │ └── page.tsx │ ├── layout.tsx │ └── page.tsx ├── components │ ├── ui │ └── common ├── hooks ├── lib └── types

4. Why this structure works well

This setup aligns with the colocation-first philosophy of Next.js App Router:

  • Feature code lives near the route
  • Shared UI lives in components
  • Business utilities live in lib
  • Global types are centralized

This keeps the project scalable while avoiding “mega global folders.”