5 Next.js Folder Structures That Scale
Picking the best next js folder structure for a production Next.js app can feel like choosing a house frame before you’ve even poured the foundation.

Picking the best next js folder structure for a production Next.js app can feel like choosing a house frame before you’ve even poured the foundation. Too rigid and you fight the routing model. Too loose and your codebase becomes a haunted house where imports rot in the dark. At techpotions, we’ve just finished shipping a multi‑tenant SaaS analytics dashboard—real users, real server actions, real pain. We tried five distinct folder strategies, broke things in staging, and kept the ones that survived. Here’s what we learned, tradeoffs included.
Why the best next js folder structure depends on your app’s growth trajectory
Before you rearrange app/, remember that Next.js itself doesn’t mandate a single layout. It supports an optional src folder, and the router can sit inside app/ or be nested deeper. Owais’s architecture guide wisely points out that architecture is four decisions—rendering, boundaries, data access, and colocation—not just a folder tree. So the best next js folder structure is the one that answers those decisions for your team.
Our own dashboard project cycled through these five, each solving a different phase of the product. The real test came when three engineers started shipping in parallel and server actions multiplied.
1. Flat app/ with Colocated Server Actions (The Starter Trap)
Attribute | Details |
|---|---|
Best for | MVPs, solo devs, or internal tools that need speed over scale. |
Tradeoff | Routes and logic fuse into a mess that’s impossible to test or refactor. |
When we bootstrapped the dashboard, we dumped everything inside app/: a dashboard/layout.tsx next to a serverActions.ts that handled every mutation. For the first two months it felt frictionless—one click, one file. The problem hit when we needed to reuse the same data‑fetching function from a cron job. We couldn’t import an app/ server action from outside the router without hacks, so we duplicated logic. Then the layout grew into a 1,200‑line God component, and even the official Next.js docs emphasize that app/ is primarily for routing—not shared business logic.
When it broke for us: A teammate renamed a layout.tsx and accidentally untangled the React context tree. 15 pages went blank in production. That’s when we knew we needed co‑location that is decoupled from the route hierarchy.
This approach aligns with small‑to‑medium projects, but if you’re scaling, our web development services team often recommends extracting logic early.
2. src/ with Route‑Based Colocation (The Official Safety Net)
Attribute | Details |
|---|---|
Best for | Teams that want a clean separation between config and code, without over‑engineering. |
Tradeoff | Still allows business logic to leak into the router if you aren’t disciplined. |
Next.js encourages putting application code inside an optional src directory. We moved to src/app/ and kept all config, tests, and utilities at root. The app/ directory now contained only route segments and UI components, while src/lib/ held our Prisma client and auth helpers. It felt like a step up—our Next.js development company uses this foundation for most client projects because it’s easy to explain.
What stuck: Having src/hooks/ and src/utils/ outside the router meant we could import { useAuth } from '@/hooks/auth' from any route without path‑traversal acrobatics. The best next js folder structure for maintainability often starts right here.
What still hurt: Server actions for a specific dashboard widget lived in src/app/dashboard/actions.ts. When the widget moved to a new URL, the actions didn’t move with it—or worse, they stayed, coupling an old route to fresh business logic. This showed us we needed to group by feature, not by URL.
3. Feature‑First with src/features/ (The Team Aligner)
Attribute | Details |
|---|---|
Best for | Multi‑team applications where features own their full lifecycle: UI, API, types, tests. |
Tradeoff | Hard to enforce without linting, and newcomers over‑decompose into micro‑features prematurely. |
After the server‑action scattering incident, we adopted a structure popularized in large‑scale Next.js guides:
src/
├── features/
│ ├── analytics/
│ │ ├── api/ # server actions, tRPC routers, services
│ │ ├── components/ # Dashboard widgets, charts
│ │ ├── hooks/ # client‑side state
│ │ └── __tests__/
│ └── billing/
│ ├── api/
│ ├── components/
│ └── ...
└── app/ # thin wrapper, mapping URLs → featuresThe app/dashboard/page.tsx became a three‑liner that imported <AnalyticsDashboard /> from @/features/analytics. Server actions lived in src/features/analytics/api/actions.ts and were importable from cron jobs, webhooks, and even a separate NestJS worker we ran. This isolation allowed two teams to work on analytics and billing simultaneously without merge conflicts.
What broke in production: We initially created 42 feature folders, many housing a single component. A mid‑sized app with a dozen clear domains like ours didn’t need that granularity; it added indirection without value. We later merged related features—turning chart‑renderer, export‑csv, and date‑picker into one analytics domain with internal sub‑folders. The best next js folder structure balances colocation with sanity.
Real‑world lesson: Only create a feature folder when a distinct user story and a dedicated data source exist. Otherwise, leave it in src/components/shared.
4. Hybrid: Feature Folders Inside app/ Route Groups (The Next.js Power Move)
Attribute | Details |
|---|---|
Best for | Teams that want the route‑awareness of App Router with the modularity of feature‑first, without a separate |
Tradeoff | Even stricter discipline required; you’re one |
The App Router’s route groups ((group)/) let you colocate feature folders directly next to their pages, without affecting the URL. We experimented with:
src/app/(dashboard)/
├── _features/
│ ├── analytics/
│ └── billing/
├── analytics/page.tsx # imports from _features/analytics
└── layout.tsxThe _features/ prefix tells Next.js to ignore it as a route, so it becomes pure organization. This felt like the best next js folder structure for smaller apps that still want feature isolation but hate the mental model of a separate src/features/ universe. It also keeps server actions physically close to the page, reducing the “where is this action?” confusion.
The production scare: A junior developer accidentally imported a client component from _features/ into a server layout. The build didn’t complain—it just silently made the whole dashboard a client tree, killing our previous SSR performance. We fixed it with a barrel‑file convention and a custom ESLint rule, but it reminded us that physical proximity can erode careful boundaries.
Verdict: Use this only when you have a rock‑solid use client boundary rule and a small enough codebase to police it manually.
5. Atomic Design + Domain (The Enterprise Drift)
Attribute | Details |
|---|---|
Best for | Design systems that evolve alongside the app, or when a shared component library must stay independent of any single feature. |
Tradeoff | Overwhelming for product engineers; often results in premature abstraction and endless restructuring debates. |
We saw the Reddit thread and the Atomic Design guide and decided to try applying it to our dashboard. The result was a src/lib/atoms/, src/lib/molecules/, src/lib/organisms/ hierarchy, with domain logic still living in src/features/. The idea was that the design system would be a pure UI layer, and features would compose them.
What worked: Our design team loved it because they could see every button, input, and card in a Storybook that mirrored the folder tree. The shared component structure kept branding consistent across six external partners who white‑labeled the dashboard.
What broke: Product engineers started building features inside the atomic folders because the boundaries were too abstract. We’d find analyticsCard in molecules/ that imported a server action directly, breaking the separation contract. Eventually, we killed the atomic prefix and kept a plain src/components/ui/ with a flat structure; feature teams never got lost again.
Real‑world take: Atomic Design is a design‑system pattern first, not a file‑system one. Enforce it with tooling—Storybook, ESLint import boundaries—or not at all.
Structuring decisions that saved us more than any folder name
Regardless of the shape you pick, three rules emerged from our gut punches:
- Colocate by concern, not by type. Don’t put all
actions.tsin a globalapi/folder; put the actions that serve a feature with that feature. The best next js folder structure is a map of your product, not a taxonomy of file extensions. - Barrel files are your friend. A single
index.tsper domain exports only the public API; internal helpers stay private. This gives you a hard module boundary without a monorepo. - Lint your imports. We added
eslint-plugin-importrules that forbid featureAfrom importing from featureB’s internals. It catches the sprawl early.
Still not sure which path fits your app? Our Next.js development company has built 40+ production apps and we pick the structure on the first sprint—so you don’t have to refactor three months later. Explore our web projects to see the patterns in action.
FAQ
What is the best Next.js folder structure for a large app with many teams?
The most scalable approach for large teams is feature‑first in src/features/, with co‑located route trees, UI components, and data access. It keeps related code together so features can evolve independently without creating merge hell.
Should I use a src folder in my Next.js project?
Yes, if you use the optional src folder, only the app/ (or pages/) router lives inside src while everything else—like tests, fixtures, and local utils—stays root‑level. This keeps your codebase tidy and aligns with the official convention.
When should I refactor my existing Next.js folder structure?
If routes are growing fast and server actions are sprinkled across app/ directories, it’s time. A clear sign is when you can’t refactor a feature without touching 10 different folders. Extraction into src/features/ makes boundaries explicit.