Back to blog
·6 min read

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:

  • app/ contains route handlers and pages only. No business logic.
  • components/ holds all reusable React components, organized by feature or type
  • lib/ contains all utilities, helpers, and business logic
  • types/ has TypeScript types and interfaces
  • hooks/ has custom React hooks
  • styles/ has global styles
  • 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:

  • Understand where utilities should go
  • Know what's already implemented
  • Find existing patterns to follow
  • Avoid duplicating logic
  • 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

  • Don't create a 200-file components folder with no organization
  • Don't maintain a 2,000-line utils.ts file
  • Don't nest folders seven levels deep
  • Don't put business logic in page components
  • Don't mix utilities across three different folders
  • Don't use relative imports across the codebase
  • 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