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 interfacesBelow 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-
- Make executable:
chmod +x setup-next-structure.sh-
- Run inside your project root:
./setup-next-structure.shSmall improvement (useful in real projects)
Many developers also add:
_components
_hooks
_lib
_typesinside 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
└── types4. 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.”