My Complete Next.js Folder Structure for Large Projects
After three painful refactors, this is the exact Next.js folder structure I now use for every large project. Copy it and save yourself the mess.
Zeeshan Zakir

Six months into one of my bigger Next.js projects, I opened my components folder and counted the files. Forty-three. Flat. No subfolders. Somewhere in there was a file called Card2.tsx and I genuinely could not tell you what made it different from Card.tsx.
That was the day I stopped and restructured everything. Then, on the next project, I restructured again because my first system had its own problems. The structure below is the third iteration, and it's the one that has actually survived contact with a large codebase. I now start every serious project with it.
The full tree
src/
├── app/
│ ├── (marketing)/ # public pages: landing, blog, pricing
│ │ ├── page.tsx
│ │ └── blog/
│ ├── (app)/ # logged-in product area
│ │ ├── layout.tsx # auth check lives here
│ │ ├── dashboard/
│ │ │ ├── page.tsx
│ │ │ └── _components/ # components used ONLY by dashboard
│ │ └── settings/
│ ├── (admin)/ # admin panel, separate layout
│ ├── api/ # route handlers (webhooks etc.)
│ ├── layout.tsx
│ └── globals.css
├── components/
│ ├── ui/ # dumb, reusable: Button, Input, Modal
│ └── shared/ # smart, cross-feature: Navbar, UserMenu
├── features/
│ ├── billing/
│ │ ├── components/
│ │ ├── actions.ts # server actions for this feature
│ │ └── queries.ts # data fetching for this feature
│ └── inventory/
├── lib/
│ ├── supabase/
│ │ ├── server.ts # server client
│ │ ├── client.ts # browser client
│ │ └── middleware.ts # session refresh helper
│ └── utils.ts
├── hooks/ # generic hooks: useDebounce, useMediaQuery
├── types/ # shared TypeScript types
└── config/ # site config, nav items, constantsLet me explain the decisions that matter, because the tree alone doesn't show why.
Rule 1: app/ is for routing, nothing else
This is the rule that fixed most of my mess. The app directory answers exactly one question: what URLs exist? Pages stay thin — they fetch data and compose components. The moment a page.tsx crosses roughly a hundred lines, something is living there that should live elsewhere.
Route groups — the folders in parentheses — let me give the marketing site, the product, and the admin panel completely different layouts without affecting URLs. The (app) layout is also where the auth check happens once, instead of in every page.
Rule 2: three kinds of components, three homes
This distinction ended my forty-three-file folder problem:
components/ui— dumb building blocks. A Button doesn't know what app it's in.components/shared— smart but cross-feature. The Navbar knows about the user, but every page uses it.app/.../_components— used by exactly one route. The underscore keeps Next.js from treating it as a route, and colocation means when I delete the route, its components go with it.
The test I use: "If I delete this feature, does this component die with it?" If yes, it lives next to the feature. If no, it goes up the tree.
Rule 3: features own their logic
The features/ folder is the part people push back on, and the part I'd defend hardest. Billing logic — its components, its server actions, its queries — lives together in features/billing. Before this, my server actions were scattered in one giant actions.ts that hit six hundred lines and made merge conflicts a daily event when I brought a second developer in.
Now "where's the code for X?" has one answer, and two people can work on two features without touching the same files.
Rule 4: two Supabase clients, never one
If you use Supabase with the App Router, you need the server client and the browser client as separate files — lib/supabase/server.ts and lib/supabase/client.ts. Early on I had one clever file that tried to detect its environment. It worked until it didn't, and the failure mode was cookies silently not being read on the server. Two boring files, zero cleverness, zero problems since.
The mistakes that shaped this structure
Barrel files everywhere. I used to put an index.ts in every folder re-exporting everything, so imports looked pretty. Then I learned they can drag unrelated code into bundles and slow the dev server on big projects. I removed them from all but components/ui and both build times and my tree-shaking improved.
Organizing by file type instead of by feature. All actions in one folder, all queries in another, all components in a third — it sounds tidy, but changing a single feature meant touching four distant folders. Grouping by feature matches how work actually happens.
Deep relative imports. ../../../../lib/utils is a smell. Set the path alias once in tsconfig.json and never think about it again:
{
"compilerOptions": {
"paths": { "@/*": ["./src/*"] }
}
}Creating structure for imaginary scale. My second iteration had empty folders "for later" — a services/ layer nothing used, a repositories/ abstraction with one file. Delete speculative structure. Add folders when a real file needs a home, not before.
Does a small project need all this?
Honestly, no. A landing page with a contact form can be three folders. The structure above earns its keep somewhere around the point where you have authentication, more than one feature area, or more than one developer. What I'd say is: adopt Rule 1 (thin app/) and the path alias from day one on every project, because retrofitting those is the painful part. The rest can grow in as the project does.
The real goal isn't a beautiful tree. It's that six months from now, tired and in a hurry, you can find the file you need in five seconds and know with confidence what deleting it will break. Card2.tsx taught me that. There is no Card2.tsx anymore.
Need help building this?
I offer full-stack development services for startups and product teams.
If you want a faster path from idea to shipped product, I can help with architecture, frontend systems, backend APIs, and launch-ready builds.
View ServicesShare this post
Related posts
More practical reading from the blog to keep your momentum going.

How I Debug Production Errors in Next.js
Production errors don't come with stack traces. Here's my exact debugging workflow for Next.js — digests, logs, Sentry, and hydration bugs.

How I Built Authentication in Next.js Without NextAuth
NextAuth kept fighting me, so I built auth with Supabase and @supabase/ssr instead. Cookies, middleware, protected routes — the full setup.

How I Built an Inventory Management System Using Next.js and Supabase
I built a complete inventory system for a client with Next.js and Supabase. The schema, the race-condition mistake, and what I'd do differently.
