How to Structure a Next.js 15 Project That Scales Without Getting Lost
Your Next.js project starts clean but becomes a tangled mess of nested folders and unclear imports. Here's how to structure directories that stay organized as your app grows—and why folder organization matters when using AI code generation.
You've started a new Next.js 15 project. The first week feels great. Everything is organized. Then you hit month two and something breaks: you can't find anything anymore.
Your components folder has 247 files. Your utils folder is 3,000 lines of code across a dozen files with unclear dependencies. You have three different places where authentication logic lives. When Claude Code or Cursor generates new files, they guess at where things should go—sometimes putting utilities in the wrong folder, duplicating logic in three places.
This isn't a problem unique to AI-assisted development, but it's a critical one. When you're working with AI code generation tools, poor structure makes it harder for the model to understand your codebase and generate correct code. The cleaner your structure, the better your generated code will be.
Let's fix this. Here's how to structure a Next.js 15 project that stays maintainable at scale.
The Core Problem with Next.js Structure
The App Router in Next.js 15 introduced flexibility that became a burden. Without strong conventions, three developers on the same team will organize the same project three different ways.
Some developers treat the app directory like a dumping ground for everything. Others put business logic everywhere. A few create elaborate nested folder hierarchies that require a treasure map to navigate.
The result: nobody can find anything. New developers spend their first week just learning where code lives. AI tools can't reliably generate new features because the structure isn't clear.
The Production-Ready Structure
Here's a folder layout that works for most SaaS applications and keeps your codebase sane as you grow:
```
src/
├── app/
│ ├── (auth)/
│ │ ├── login/
│ │ │ └── page.tsx
│ │ ├── signup/
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── (dashboard)/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ ├── settings/
│ │ │ └── page.tsx
│ │ └── projects/
│ │ ├── page.tsx
│ │ └── [id]/
│ │ └── page.tsx
│ ├── api/
│ │ └── auth/
│ │ └── [...nextauth]/
│ │ └── route.ts
│ └── layout.tsx
├── components/
│ ├── ui/
│ │ ├── Button.tsx
│ │ ├── Card.tsx
│ │ ├── Dialog.tsx
│ │ └── index.ts
│ ├── auth/
│ │ ├── LoginForm.tsx
│ │ ├── SignupForm.tsx
│ │ └── index.ts
│ ├── dashboard/
│ │ ├── ProjectsList.tsx
│ │ ├── StatsCard.tsx
│ │ └── index.ts
│ └── shared/
│ ├── Navbar.tsx
│ ├── Sidebar.tsx
│ └── index.ts
├── lib/
│ ├── auth/
│ │ ├── getSession.ts
│ │ ├── validateToken.ts
│ │ └── index.ts
│ ├── db/
│ │ ├── queries.ts
│ │ ├── schema.ts
│ │ └── index.ts
│ ├── api/
│ │ ├── client.ts
│ │ └── index.ts
│ └── utils.ts
├── types/
│ ├── auth.ts
│ ├── database.ts
│ └── api.ts
├── hooks/
│ ├── useAuth.ts
│ ├── useUser.ts
│ └── index.ts
└── styles/
└── globals.css
```
This structure follows clear rules:
Key Principles to Follow
### Group by Feature, Not by Type
Don't create a folder for "utilities" and throw everything in there. Instead, group related functions together. You should have lib/auth/, lib/db/, lib/api/ so each folder contains related logic.
When you need to update authentication logic, everything lives in one place. When Claude Code generates new auth utilities, it knows where to put them.
### Use Index Files for Clean Imports
In your components/auth/ folder, create an index.ts that re-exports everything:
```
export { LoginForm } from './LoginForm'
export { SignupForm } from './SignupForm'
```
This lets you import with clean paths:
```
import { LoginForm, SignupForm } from '@/components/auth'
```
Instead of:
```
import LoginForm from '@/components/auth/LoginForm'
import SignupForm from '@/components/auth/SignupForm'
```
It's a small detail that makes imports readable across 500 files.
### Keep Files Small and Focused
If a component file exceeds 300 lines, split it. If a utility file exceeds 200 lines, it's doing too much. Break it up.
Small, focused files are easier for AI tools to understand. When Claude Code sees a 50-line component, it understands the entire thing instantly. When it sees a 400-line file, it might miss context.
### Use Aliases to Avoid Relative Imports
Configure your tsconfig.json:
```
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
```
Now you can import from anywhere:
```
import { Button } from '@/components/ui'
import { getSession } from '@/lib/auth'
```
Never use relative paths like ../../../lib/auth. Aliases stay correct when files move.
Why This Matters for AI Code Generation
When you're using Claude Code, Cursor, or other AI assistants, the tool's understanding of your codebase affects code quality. A messy structure makes it harder for the model to:
Clean structure makes AI-assisted development faster and more reliable. The model can look at your lib/auth/ folder and understand all authentication logic is centralized there.
This is why tools like ZipBuild that generate scaffold code with the right structure from the start save weeks of reorganization work later. You get a codebase that grows cleanly instead of one that needs refactoring after the first month.
The Anti-Patterns You're Definitely Avoiding Now
Start Clean Now
If you're building a new Next.js 15 project, use this structure from day one. If you're working with an existing codebase, spend time reorganizing before it becomes unmaintainable.
The investment in structure pays dividends when you're shipping features consistently, onboarding new developers smoothly, and working with AI tools that actually understand your code.
Try the free discovery chat at zipbuild.dev to see how production-ready scaffold generation with the right structure can jumpstart your SaaS project.
Written by ZipBuild Team
Ready to build with structure?
Try the free discovery chat and see how ZipBuild architects your idea.
Start Building