Introduction
Choosing the right folder structure is one of the first architectural decisions you make when building a Next.js application.
A small application can work with almost any structure. But as the project grows, an unclear folder structure can make development slower, create duplicated code, and make it difficult to understand where new functionality belongs.
There isn't one folder structure that works for every Next.js project.
However, for a medium-to-large Next.js application, a feature-based architecture combined with the App Router provides a strong foundation for scalability and maintainability.
In this guide, we'll build a practical Next.js folder structure and explain what should go inside each directory.
Recommended Next.js Folder Structure
For a large Next.js application, I recommend starting with this structure:
my-next-app/
├── public/
│
├── src/
│ ├── app/
│ │ ├── (auth)/
│ │ │ ├── login/
│ │ │ │ └── page.tsx
│ │ │ └── register/
│ │ │ └── page.tsx
│ │ │
│ │ ├── (dashboard)/
│ │ │ ├── dashboard/
│ │ │ │ └── page.tsx
│ │ │ ├── projects/
│ │ │ │ └── page.tsx
│ │ │ └── settings/
│ │ │ └── page.tsx
│ │ │
│ │ ├── api/
│ │ │ └── projects/
│ │ │ └── route.ts
│ │ │
│ │ ├── layout.tsx
│ │ ├── loading.tsx
│ │ ├── error.tsx
│ │ └── not-found.tsx
│ │
│ ├── features/
│ │ ├── auth/
│ │ │ ├── components/
│ │ │ ├── hooks/
│ │ │ ├── services/
│ │ │ ├── schemas/
│ │ │ └── types.ts
│ │ │
│ │ ├── projects/
│ │ │ ├── components/
│ │ │ ├── hooks/
│ │ │ ├── services/
│ │ │ ├── schemas/
│ │ │ └── types.ts
│ │ │
│ │ ├── users/
│ │ └── billing/
│ │
│ ├── components/
│ │ ├── ui/
│ │ ├── layout/
│ │ └── shared/
│ │
│ ├── lib/
│ │ ├── db/
│ │ ├── auth/
│ │ ├── api/
│ │ ├── validation/
│ │ └── logger/
│ │
│ ├── hooks/
│ ├── config/
│ ├── types/
│ └── styles/
│
├── .env.local
├── next.config.ts
├── package.json
├── tsconfig.json
└── eslint.config.mjsThis structure separates routing, features, shared UI, infrastructure, and configuration.
Let's break it down.
1. app/ — Routes and Pages
The app directory is responsible for routing and route-level concerns.
app/
├── (auth)/
├── (dashboard)/
├── api/
├── layout.tsx
├── loading.tsx
├── error.tsx
└── not-found.tsxKeep the app directory focused on Next.js routing rather than putting all application logic inside it.
A page should primarily compose the UI and data required for that route.
For example:
export default async function ProjectsPage() {
const projects = await getProjects();
return <ProjectsView projects={projects} />;
}The route defines where the page lives.
The feature defines what the page does.
2. Route Groups
Next.js route groups allow you to organize routes without changing the URL.
For example:
app/
├── (auth)/
│ ├── login/
│ └── register/
│
└── (dashboard)/
├── dashboard/
├── projects/
└── settings/The parentheses mean these folders are not included in the URL.
This is useful when different sections of your application need different layouts or organization.
3. features/ — Business Features
This is the most important part of the architecture.
Instead of putting all components, hooks, services, and schemas into global folders, organize application-specific code by feature.
features/
├── projects/
├── users/
├── billing/
└── notifications/A feature can contain everything related to that domain:
features/projects/
├── components/
├── hooks/
├── services/
├── schemas/
├── types.ts
└── index.tsFor example:
features/projects/
├── components/
│ ├── project-card.tsx
│ ├── project-list.tsx
│ └── project-form.tsx
│
├── hooks/
│ └── use-projects.ts
│
├── services/
│ ├── get-projects.ts
│ ├── create-project.ts
│ └── delete-project.ts
│
├── schemas/
│ └── project.schema.ts
│
└── types.tsNow, when you need to modify the projects feature, most of the code is in one predictable location.
4. components/ — Shared UI
The global components directory should contain components that are genuinely shared across multiple features.
components/
├── ui/
│ ├── button.tsx
│ ├── input.tsx
│ ├── dialog.tsx
│ └── dropdown.tsx
│
├── layout/
│ ├── header.tsx
│ ├── sidebar.tsx
│ └── footer.tsx
│
└── shared/
├── empty-state.tsx
├── loading-state.tsx
└── error-state.tsxA useful rule is:
If only one feature uses a component, keep it inside that feature.
For example:
features/projects/components/project-card.tsxrather than:
components/project-card.tsxThis prevents the global components folder from becoming a dumping ground.
5. lib/ — Infrastructure
The lib directory is a good place for application-wide infrastructure.
lib/
├── db/
├── auth/
├── api/
├── validation/
└── logger/Examples include:
Database clients
Authentication utilities
API clients
Logging
Shared validation helpers
External service integrations
These are not specific to one feature.
Multiple features can depend on them.
6. hooks/ — Truly Global Hooks
I recommend being careful with a global hooks directory.
If a hook belongs specifically to a feature, keep it there:
features/projects/hooks/use-projects.tsUse the global directory for hooks that are genuinely reusable across unrelated features:
hooks/
├── use-media-query.ts
├── use-debounce.ts
└── use-local-storage.tsThis keeps ownership clear.
7. types/ — Shared Types
Types that are shared across multiple parts of the application can live here.
types/
├── api.ts
├── pagination.ts
└── common.tsBut feature-specific types should stay inside the feature:
features/projects/types.tsDon't create a giant global types file containing every type in the application.
8. config/ — Application Configuration
For configuration that is shared across the application:
config/
├── site.ts
├── navigation.ts
└── constants.tsFor example:
export const siteConfig = {
name: "My Application",
description: "A modern Next.js application",
};Environment variables should remain in .env.local and should be accessed through a controlled configuration layer when appropriate.
9. Server Components vs Client Components
A good folder structure isn't enough.
You also need a clear strategy for Server and Client Components.
With the App Router, Server Components should generally be your starting point.
Use a Client Component when you need things such as:
useStateuseEffectBrowser APIs
Event handlers
Client-only libraries
Interactive UI
For example:
Dashboard
├── Server Component
│ ├── Server-rendered data
│ └── Statistics
│
└── Client Component
└── InteractiveChartDon't add "use client" to an entire page just because one small component requires client-side interactivity.
Keep the client boundary as small as practical.
10. Where Should API Logic Go?
There are two different responsibilities here.
The route handler belongs in:
app/api/projects/route.tsBut business logic can live inside:
features/projects/services/For example:
app/api/projects/route.ts
↓
features/projects/services/create-project.ts
↓
lib/db/This keeps HTTP concerns separate from business logic and infrastructure.
11. Where Should Validation Go?
Feature-specific validation should usually live with the feature.
features/projects/
└── schemas/
├── create-project.schema.ts
└── update-project.schema.tsThis makes validation rules easy to find.
It also prevents a massive global schemas directory from becoming difficult to navigate.
12. Avoid the Giant utils/ Folder
A common pattern in growing projects is:
utils/
├── helper.ts
├── common.ts
├── format.ts
├── transform.ts
├── misc.ts
└── something.tsThis doesn't communicate ownership.
Instead, ask:
Who owns this code?
If it's project-specific:
features/projects/If it's infrastructure:
lib/If it's a genuinely reusable hook:
hooks/If it's shared UI:
components/Good architecture isn't about having more folders.
It's about making ownership obvious.
13. Avoid Overengineering Small Projects
This structure is designed for medium-to-large applications.
You don't need all of these folders on day one.
For a small application, this can be perfectly reasonable:
src/
├── app/
├── components/
└── lib/As the application grows, introduce:
features/when feature boundaries become useful.
Architecture should evolve with the complexity of the application.
14. Recommended Dependency Direction
A useful mental model is:
app
↓
features
↓ ↓
components libIn other words:
apphandles routesfeatureshandle business functionalitycomponentsprovide shared UIlibprovides infrastructure
Try to avoid infrastructure depending on individual application features.
This makes the dependency graph easier to understand and reduces circular dependencies.
15. Final Recommended Structure
For a production-scale Next.js application, my recommended starting point is:
src/
├── app/ # Routes and layouts
├── features/ # Business features
├── components/ # Shared UI
├── lib/ # Infrastructure
├── hooks/ # Global reusable hooks
├── types/ # Shared types
├── config/ # Application configuration
└── styles/ # Global stylesThe key principle is simple:
Organize your application around features, not just file types.
Instead of asking:
"Where should I put this component?"
ask:
"Which part of the application owns this component?"
That small change in thinking makes a big difference as a Next.js codebase grows.
Conclusion
There is no single "perfect" Next.js folder structure.
The best structure is the one that makes your codebase easy to navigate, keeps responsibilities separated, and makes it obvious where new code belongs.
For small projects, keep things simple.
For larger applications, a feature-based structure with the App Router provides a strong foundation:
app → routes
features → business logic
components → shared UI
lib → infrastructure
hooks → shared client utilities
types → shared types
config → configurationThe goal isn't to create the most sophisticated folder structure.
The goal is to create a structure that makes your application easy to understand, easy to change, and easy to scale.
