
As React applications scale, your folder structure transitions from being a matter of personal aesthetics to a foundational architectural decision. With the introduction of the Next.js App Router, the old mental models broke down. We moved away from the legacy pages/ directory (which often turned into a dumping ground for both routing and UI components) towards an opinionated, convention-based routing system inside app/.
However, the App Router's flexibility, specifically its support for colocation, introduces notable architectural trade-offs. Without clear structural guardrails, your app/ directory will quickly balloon with hundreds of mixed files, circular dependencies, and leaked Server/Client boundaries.
In this guide, we break down production-tested folder structures for Next.js App Router applications, moving from basic colocation to Feature-Sliced Design (FSD) for enterprise applications.
app/ Directory Is ONLY for Routing: Treat app/ strictly as an HTTP router and page compositor (page.tsx, layout.tsx, loading.tsx, error.tsx). Never dump heavy business logic or global utility libraries here.(marketing) & Private Folders _components: Group related screens without impacting URL paths, and prefix route-local helper folders with _ to guarantee they are never exposed as public endpoints.src/features/[feature-name]/api, ui/, hooks/) or Layered Flat (src/components/, src/lib/) for reusable domain logic to keep Server and Client component boundaries crystal clear.The App Router treats the app/ directory strictly as an HTTP router and page compositor. Only special convention files are publicly accessible:
page.tsx: Unique UI for a route, accessible via URL.layout.tsx: Shared UI that wraps children and preserves state across navigations.loading.tsx: Streaming fallback powered by React Suspense.error.tsx: Client-side error boundary catching run-time errors in route subtrees.not-found.tsx: UI for 404 states.route.ts: Server-side API endpoint (GET, POST, PUT, DELETE).Everything else inside a route folder can technically be colocated:
app/
└── dashboard/
├── page.tsx
├── layout.tsx
├── DashboardGraph.tsx # Colocated component
├── dashboard.module.css # Colocated styling
└── dashboard.test.tsx # Colocated unit test
While colocating everything inside app/ works well for MVPs, it falls apart in medium-to-large projects:
app/reports/page.tsx) needs DashboardGraph, where does it live? Moving it up creates unpredictable import paths.app/ folder with 30 routes and 200 colocated files makes finding routes during an on-call incident excruciating.'use client' to parent wrappers, de-optimizing Server Component streaming.| Model | Best For | Strengths | Trade-offs |
|---|---|---|---|
| 1. Colocated App | MVPs & Small Sites (<10 routes) | Fast setup, zero mental overhead | Spaghetti imports when sharing code across routes |
| 2. Layered Flat | Startups & Mid-size Apps (10-40 routes) | Familiar to standard React teams | Giant components/ and hooks/ folders become junk drawers |
| 3. Feature-Sliced (FSD) | Enterprise & Scale (40+ routes, multi-team) | Strict unidirectional boundaries, decoupled features | Initial learning curve for junior developers |
Next.js provides two vital folder conventions that keep your routing cleanly separated from internal logic:
(folder)Wrapping a folder name in parentheses organizes routes logically without altering the public URL:
app/
├── (marketing)/
│ ├── layout.tsx # Marketing layout (Navigation bar + Footer)
│ ├── page.tsx # Served at: /
│ └── pricing/
│ └── page.tsx # Served at: /pricing
└── (dashboard)/
├── layout.tsx # Dashboard layout (Sidebar + Account switcher)
├── settings/
│ └── page.tsx # Served at: /settings
└── analytics/
└── page.tsx # Served at: /analytics
Why use Route Groups?
_folderPrefixing a folder name with an underscore tells Next.js to opt that folder out of routing entirely:
app/
├── (dashboard)/
│ └── analytics/
│ ├── _components/ # OPTED OUT of routing (/analytics/_components is 404)
│ │ ├── Chart.tsx
│ │ └── MetricCard.tsx
│ ├── _lib/
│ │ └── calculations.ts
│ └── page.tsx # Served at: /analytics
Use _components or _lib when you want to colocate code that is strictly private to a single route and will never be reused elsewhere.
For mission-critical production applications, we separate Next.js routing from domain business logic using a 4-layer architecture:
src/
├── app/ # Layer 1: Next.js routing, layouts, and page composition
├── features/ # Layer 2: Business actions and user workflows
├── entities/ # Layer 3: Domain data models and business entities
└── shared/ # Layer 4: Foundation (UI primitives, API clients, helpers)
The most important rule in Feature-Sliced Design is unidirectional dependencies. Code can only import from layers strictly below it:
app/ Layer: Pure CompositionThe app/ directory contains zero business logic. It only:
// src/app/(dashboard)/analytics/page.tsx
import { Suspense } from 'react';
import { AnalyticsMetrics } from '@/features/usage-metrics';
import { getCurrentUser } from '@/entities/user';
import { SkeletonLoader } from '@/shared/ui';
export default async function AnalyticsPage() {
const user = await getCurrentUser();
return (
<main className="container mx-auto p-6 space-y-6">
<header>
<h1 className="text-3xl font-bold tracking-tight">Analytics</h1>
<p className="text-muted-foreground">Welcome back, {user.name}</p>
</header>
<Suspense fallback={<SkeletonLoader count={4} />}>
<AnalyticsMetrics organizationId={user.orgId} />
</Suspense>
</main>
);
}
features/ Layer: User WorkflowsFeatures contain interactive user flows that deliver measurable business value (e.g. auth-flow, checkout, team-invitation).
src/features/auth-flow/
├── ui/ # React client/server components
│ ├── LoginForm.tsx
│ └── SocialButtons.tsx
├── api/ # Colocated Server Actions
│ └── authenticate.ts
├── model/ # Local state, validation schemas
│ └── schema.ts
└── index.ts # STRICT Public API export
Always export through index.ts to keep internals private:
// src/features/auth-flow/index.ts
export { LoginForm } from './ui/LoginForm';
export { authenticateWithOAuth } from './api/authenticate';
entities/ Layer: Core Business EntitiesEntities represent real-world concepts in your domain (e.g. User, Workspace, Invoice). They hold:
<UserAvatar />, <PlanBadge />).shared/ Layer: Reusable FoundationThe shared/ directory holds domain-agnostic code that could theoretically be published as an internal npm package:
shared/ui: Primitives like Button, Modal, Input, Dropdown (often Shadcn / Radix wrappers).shared/lib: Date formatters, math utilities, network clients.shared/config: Environment variable validation (Zod) and site metadata.One of the most common causes of slow Next.js apps is "Client Component Contamination", where adding 'use client' at the top of a page forces every child component to run in client JavaScript bundles.
To avoid this, follow the "Leaves-Only Client Component" rule in your folder structure:
app/(dashboard)/dashboard/page.tsx # [Server Component] Fetches data
├── DashboardSummary.tsx # [Server Component] Pure HTML rendering
└── _components/
└── InteractiveFilter.tsx # ['use client'] ONLY interactive button/state
By keeping the parent page a Server Component and importing the Client Component into the leaf position, you minimize the client JavaScript bundle and eliminate Next.js hydration mismatch errors.
Here is a battle-tested directory structure for enterprise Next.js App Router applications:
my-app/
├── public/
│ └── static/
├── src/
│ ├── app/
│ │ ├── (auth)/
│ │ │ ├── login/page.tsx
│ │ │ ├── signup/page.tsx
│ │ │ └── layout.tsx
│ │ ├── (dashboard)/
│ │ │ ├── layout.tsx
│ │ │ ├── overview/page.tsx
│ │ │ └── settings/
│ │ │ ├── page.tsx
│ │ │ └── _components/
│ │ ├── api/
│ │ │ └── webhooks/stripe/route.ts
│ │ ├── favicon.ico
│ │ ├── global-error.tsx
│ │ ├── layout.tsx
│ │ └── not-found.tsx
│ │
│ ├── features/
│ │ ├── auth/
│ │ │ ├── ui/
│ │ │ ├── api/
│ │ │ └── index.ts
│ │ └── billing/
│ │ ├── ui/
│ │ ├── api/
│ │ └── index.ts
│ │
│ ├── entities/
│ │ ├── user/
│ │ │ ├── ui/UserAvatar.tsx
│ │ │ ├── model/types.ts
│ │ │ └── index.ts
│ │ └── organization/
│ │
│ └── shared/
│ ├── ui/ # Reusable design system components
│ ├── lib/ # Utilities (cn, formatters)
│ └── hooks/ # Domain-agnostic React hooks
│
├── middleware.ts # Edge routing & auth guards
├── next.config.mjs
└── tsconfig.json
Which folder name convention prevents files from being routed publicly while keeping them in app/?
Resolve text content mismatches, browser extension conflicts, and suppressHydrationWarning cleanly.
Compare bundle sizes and learn how to wire global state into App Router client components.
(folder) is a Route Group that groups routes without adding segments to the URL path, allowing multiple root layouts. _folder is a Private Folder that completely opts out of the routing system, preventing any child files from becoming public endpoints.
Place generic, domain-agnostic UI components (like buttons, modals, inputs) in src/shared/ui/ or src/components/ui/. Domain-specific components should live in src/features/[feature-name]/ui/ or private _components folders within a route.
Yes. In Feature-Sliced Design, we recommend colocating 'use server' action files inside features/[feature-name]/api/actions.ts. This keeps mutations close to the UI forms that trigger them while maintaining clean boundaries.
Parallel routes use the @name convention to render multiple pages simultaneously within the same layout (e.g. @modal). Intercepting routes use (.)route or (..)route to load a route from another segment within the current view without changing the URL context, ideal for photo galleries and login modals.
Next.js app/ is optimized for routing, streaming, and HTTP mechanics. Placing heavy domain logic, complex state machines, and business workflows directly in app/ causes massive file coupling, makes unit testing difficult, and leads to accidental circular dependencies.
To continue leveling up your production Next.js architecture, explore these complementary guides:
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

The comprehensive production guide to Next.js App Router: Server Components boundaries, advanced caching, streaming with Suspense, Server Actions, connection pooling, and enterprise route architecture.
Read more
Master Next.js App Router caching layers: Request Memoization, Data Cache, Full Route Cache, and Router Cache to prevent unexpected stale data bugs.
Read more
How Next.js 15 Partial Prerendering streams dynamic components inside static shells: Suspense architecture, TTFB drops, and edge caching.
Read more