How to Structure Your Next.js App Router Project for Scale
Stop shoving everything into the components folder. Learn the Feature-Sliced Design pattern adapted perfectly for the Next.js App Router.
Introduction#
Next.js App Router gives you unparalleled freedom. Aside from the mandatory routing conventions (page.tsx, layout.tsx), Next.js doesn't enforce how you should organize your UI components, hooks, utilities, or database calls.
For small projects, a simple components and lib folder works perfectly. But at scale—when your repository hits dozens of routes and millions of lines of code—the "flat folder" approach collapses into a tangled web of circular dependencies and unmaintainable code.
In 2026, the enterprise standard for Next.js organization is an adaptation of Feature-Sliced Design (FSD) natively integrated with App Router capabilities.
The "Everything in Components" Problem#
The most common Next.js beginner folder structure looks like this:
/app
/dashboard
page.tsx
/components
Button.tsx
DashboardSidebar.tsx
UserProfileCard.tsx
DatePicker.tsx
/lib
supabase.ts
utils.ts
Why does this fail at scale? Because Button.tsx (a generic UI component) lives right next to DashboardSidebar.tsx (a highly specific, domain-coupled component that likely fetches data). When a new developer joins the team, they have no idea which components are universally safe to reuse and which are deeply tied to specific pages.
The Feature-Centric Approach#
Instead of grouping files by their type (e.g., "all hooks go in a /hooks folder"), group files by their feature or domain.
Here is the ideal scalable structure:
/src
/app # only routing logic
/dashboard
page.tsx
layout.tsx
/components # purely generic, reusable UI (Design System)
/ui
Button.tsx
Input.tsx
Dialog.tsx
/features # Domain-specific modules
/auth # "Auth" feature bundle
/components
LoginForm.tsx
/actions
login.ts
/hooks
useAuth.ts
/billing # "Billing" feature bundle
/components
PricingTable.tsx
/actions
checkout.ts
/queries
get-subscription.ts
/lib # truly global utilities
supabase.ts
utils.ts
1. The app Directory (Routing Only)#
Your app directory should be surprisingly thin. It should act purely as the router and entry point.
A typical page.tsx should just parse search params, run authorization checks, and render a high-level feature component:
// app/dashboard/billing/page.tsx
import { BillingDashboard } from '@/features/billing/components/BillingDashboard'
import { getSubscriptionStatus } from '@/features/billing/queries/get-subscription'
export default async function Page() {
const status = await getSubscriptionStatus()
return <BillingDashboard initialStatus={status} />
}
2. The components/ui Directory (Design System)#
This directory should only contain dumb, stateless UI components. These components should never fetch their own data or know anything about your business logic.
If you use shadcn/ui, this is where those components live securely decoupled from your domain.
3. The features Directory (The Core)#
This is where the magic happens. A "Feature" is a self-contained slice of domain logic. By isolating auth, billing, and projects into their own directories, you enforce strict architectural boundaries.
A file inside /features/billing is absolutely forbidden from importing from /features/auth/internal.
The index.ts rule: Every feature folder should have an index.ts file that exports only what other features are allowed to use. This creates a clear public API for your modules.
// src/features/billing/index.ts
export { PricingTable } from './components/PricingTable'
export { getSubscriptionStatus } from './queries/get-subscription'
export type { SubscriptionPlan } from './types'
4. Server Actions vs Data Queries#
Notice how inside a feature folder we split /actions and /queries:
- /queries: Read operations. These are standard async functions that fetch data (usually wrapped in Next.js
cache). - /actions: Write operations. These files have the
'use server'directive at the top and handle mutations from the client.
By keeping these separate, you prevent accidentally exposing sensitive database query logic to the client side.
Why This Architecture Wins#
- Delete-ability: If you decide to remove a feature (e.g., a "Referral" system), you just delete the
/features/referralsfolder, rather than hunting for scattered hooks, components, and API routes across ten global folders. - Onboarding: New developers know exactly where to look. Need to fix a bug in billing? You don't scour the global
componentsfolder; you go straight tofeatures/billing. - Colocation: Tests live right next to the code they test. Server actions live right next to the components that call them.
Adopting a feature-centric structure early feels like overkill, but by the time your Next.js app hits production, it will be the only thing keeping your codebase sane.
Continue Reading
AI Coding Assistants: Revolution or Hype?
A realistic look at how GitHub Copilot, ChatGPT, and other AI tools are actually changing software development workflows.
Building Offline-First Apps with Next.js and Supabase
Learn how to build offline-first applications with Next.js and Supabase. Implement local-first data sync, conflict resolution, and seamless offline/online transitions.
Debugging Supabase RLS Issues: A Step-by-Step Guide
Master RLS debugging techniques. Learn how to identify, diagnose, and fix Row Level Security policy issues that block data access in production.
Browse by Topic
Find stories that matter to you.