Best Next.js Folder Structure for Large-Scale Applications

Vrushik
11 min read
ReactNext.js App RouterTypeScriptWeb DevelopmentSoftware ArchitectureFrontend Architecture
Best Next.js Folder Structure for Large-Scale Applications

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:

javascript
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.mjs

This 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.

javascript
app/ ├── (auth)/ ├── (dashboard)/ ├── api/ ├── layout.tsx ├── loading.tsx ├── error.tsx └── not-found.tsx

Keep 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:

javascript
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:

javascript
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.

javascript
features/ ├── projects/ ├── users/ ├── billing/ └── notifications/

A feature can contain everything related to that domain:

javascript
features/projects/ ├── components/ ├── hooks/ ├── services/ ├── schemas/ ├── types.ts └── index.ts

For example:

javascript
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.ts

Now, 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.

javascript
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.tsx

A useful rule is:

If only one feature uses a component, keep it inside that feature.

For example:

javascript
features/projects/components/project-card.tsx

rather than:

javascript
components/project-card.tsx

This prevents the global components folder from becoming a dumping ground.

5. lib/ — Infrastructure

The lib directory is a good place for application-wide infrastructure.

javascript
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:

javascript
features/projects/hooks/use-projects.ts

Use the global directory for hooks that are genuinely reusable across unrelated features:

javascript
hooks/ ├── use-media-query.ts ├── use-debounce.ts └── use-local-storage.ts

This keeps ownership clear.

7. types/ — Shared Types

Types that are shared across multiple parts of the application can live here.

javascript
types/ ├── api.ts ├── pagination.ts └── common.ts

But feature-specific types should stay inside the feature:

javascript
features/projects/types.ts

Don'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:

javascript
config/ ├── site.ts ├── navigation.ts └── constants.ts

For example:

javascript
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:

  • useState

  • useEffect

  • Browser APIs

  • Event handlers

  • Client-only libraries

  • Interactive UI

For example:

javascript
Dashboard ├── Server Component │ ├── Server-rendered data │ └── Statistics └── Client Component └── InteractiveChart

Don'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:

javascript
app/api/projects/route.ts

But business logic can live inside:

javascript
features/projects/services/

For example:

javascript
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.

javascript
features/projects/ └── schemas/ ├── create-project.schema.ts └── update-project.schema.ts

This 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:

javascript
utils/ ├── helper.ts ├── common.ts ├── format.ts ├── transform.ts ├── misc.ts └── something.ts

This doesn't communicate ownership.

Instead, ask:

Who owns this code?

If it's project-specific:

javascript
features/projects/

If it's infrastructure:

javascript
lib/

If it's a genuinely reusable hook:

javascript
hooks/

If it's shared UI:

javascript
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:

javascript
src/ ├── app/ ├── components/ └── lib/

As the application grows, introduce:

javascript
features/

when feature boundaries become useful.

Architecture should evolve with the complexity of the application.

14. Recommended Dependency Direction

A useful mental model is:

javascript
app features ↓ ↓ components lib

In other words:

  • app handles routes

  • features handle business functionality

  • components provide shared UI

  • lib provides 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:

javascript
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 styles

The 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:

javascript
app → routes features → business logic components → shared UI lib → infrastructure hooks → shared client utilities types → shared types config → configuration

The 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.