All Articles
NextJs#Next.js#React#Next.js App Router#TypeScript

Best Next.js Folder Structure for Large-Scale Applications

Vrushik

Vrushik

Software Engineer

August 3, 20267 min read
Best Next.js Folder Structure for Large-Scale Applications
A scalable and maintainable Next.js folder structure using the App Router, feature-based architecture, Server Components, shared UI, and clear separation of business logic.

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

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:

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

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

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:

features/projects/components/project-card.tsx

rather than:

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.

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

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

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.

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

But feature-specific types should stay inside the feature:

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:

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

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

  • useState

  • useEffect

  • Browser APIs

  • Event handlers

  • Client-only libraries

  • Interactive UI

For example:

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:

app/api/projects/route.ts

But 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.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:

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:

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

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:

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.