All Articles
Frontend Architecture & Best Practices

Your Next.js Project Structure Is Slowing Your Team Down

Most teams start Next.js with a flat structure that works at 10 components and breaks at 100. Here is how to organise your project so it scales with your team instead of fighting it.

Velox Studio7 min read

Every Next.js project starts clean.

A fresh repo. A flat folder structure. Components where you can find them. Routes that make sense. The first few weeks feel fast because everything is simple and everyone knows where everything is.

Then the project grows.

New routes get added. Components multiply. A shared component gets used in five different places and nobody can remember where the original lives. Someone creates a utils folder and dumps everything in it. The components folder has forty files with no organisation. A new developer joins and spends their first three days just trying to understand where things are.

This is not a discipline problem. It is a structure problem. And the structure you started with — the flat structure that felt fine at ten components — was never designed to scale.

Why Flat Structures Break

The default Next.js project structure works perfectly for tutorials and small projects. It does not work for production codebases with multiple developers, multiple routes, and hundreds of components.

The problem is discoverability. In a flat structure, every file lives at the same level of abstraction. A button component sits next to a data fetching utility sits next to a page layout. There is no way to tell from the structure alone what a file is for, who owns it, or whether it is used in one place or twenty.

As the project grows, three things happen:

First, naming becomes a substitute for structure. Files get longer names to describe what they do because the folder they live in does not describe it for them. UserProfileCardWithAvatar.tsx instead of components/profile/Card.tsx.

Second, shared code becomes ambiguous. Is this component shared across the whole app or just in this section? Is this utility used globally or only on this page? The flat structure gives no answer.

Third, refactoring becomes expensive. Moving a component means hunting down every import across the codebase. Nobody does it. The structure stays broken.

The Structure That Actually Scales

The App Router in Next.js 13 and above gives you a much better foundation to work from — but only if you use it intentionally.

Here is the folder structure we use on every production Next.js project:

src/
  app/                    # App Router — routes only
    (marketing)/          # Route group — marketing pages
      page.tsx
      layout.tsx
    (dashboard)/          # Route group — authenticated pages
      dashboard/
        page.tsx
        _components/      # Route-specific components
          StatsCard.tsx
          RecentActivity.tsx
      settings/
        page.tsx
  components/             # Shared UI components
    ui/                   # Primitive components (Button, Input, Modal)
    layout/               # Layout components (Header, Footer, Sidebar)
    forms/                # Form components
  lib/                    # Business logic and utilities
    api/                  # API client functions
    utils/                # Pure utility functions
    hooks/                # Shared custom hooks
    validators/           # Zod or Yup schemas
  types/                  # TypeScript type definitions
  config/                 # App-wide configuration

The key decisions in this structure are intentional.

Route groups keep the app folder clean. Wrapping routes in (marketing) and (dashboard) groups lets you have separate layouts for different sections without polluting the URL. It also makes it immediately obvious which routes belong to which part of the application.

Route-specific components live with their route. The _components folder inside a route directory is one of the most underused patterns in Next.js. If a component is only used on one page, it should live next to that page. The underscore prefix tells Next.js not to treat it as a route segment. When you delete the page, you delete the components with it. No orphaned files.

Shared components are strictly categorised. The ui folder contains primitives — components with no business logic that could be dropped into any project. The layout folder contains structural components. The forms folder contains form-specific components. This categorisation means you always know where to look.

lib is not a dumping ground. Every subfolder in lib has a clear purpose. API functions in api. Pure utilities in utils. Custom hooks in hooks. Schema validators in validators. If something does not fit a category, the category probably needs to be added — not skipped.

Server Components and Client Components

The App Router introduces a distinction that your folder structure should reflect: server components and client components.

Server components run on the server. They can fetch data directly, access backend resources, and cannot use browser APIs or React hooks. Client components run in the browser and can use hooks, event handlers, and browser APIs.

The mistake most teams make is sprinkling 'use client' directives everywhere to make errors go away. This defeats the purpose of server components and pushes unnecessary JavaScript to the browser.

The pattern that works:

Keep your page components as server components by default. They fetch data and pass it as props to client components. Client components are leaves in the tree — they handle interactivity but receive their data from above.

A clear naming convention helps. Some teams use a .client.tsx suffix for client components. Others co-locate client components in a _client subfolder next to server components. Either approach is fine. What matters is that the distinction is visible in the structure.

The Shared Component Library

The components/ui folder deserves particular attention because it is the foundation everything else is built on.

These are your primitive components — Button, Input, Select, Modal, Badge, Card. They should have no business logic, no data fetching, and no opinions about where they are used. They are the building blocks.

Three rules for shared UI components:

Props over conditions. A Button component should not have twelve internal conditions for different variants. It should accept a variant prop and render accordingly. Keep the logic in the props interface, not inside the component.

Consistent naming. Every component is a noun. Button, not ClickableElement. Modal, not PopupContainer. The name describes what it is, not how it works.

Document the props. A shared component without documented props is a trap. The next developer will read the source code to understand how to use it, find it more complex than expected, and create a new component instead. One line of JSDoc per prop takes thirty seconds and saves hours.

When to Refactor

If your current project does not look like this structure, you do not need to refactor everything at once. The approach that works is incremental.

Start with the next feature you build. Set up the correct structure for that feature — route group, route-specific components, shared components in the right place. Leave the old code where it is for now.

Each new feature follows the new structure. Over time, the old flat structure shrinks as components get refactored into the right places when they are touched for other reasons.

The goal is not a perfect structure on day one. The goal is a structure that gets better over time instead of worse.

The Return on Investment

A well-structured Next.js project does one thing above everything else: it makes the next developer productive faster.

Not just an external hire. The next developer might be you, six months from now, coming back to a project you have not touched since launch. A clear structure means you can find anything in under thirty seconds. A flat structure means you spend the first hour remembering where you put things.

Structure is not overhead. It is the thing that makes everything else faster.

Is your Next.js codebase starting to feel like a maze?

We build and refactor Next.js projects for agencies and startups using a consistent, scalable structure from day one. No mess to untangle later.

View Next.js Development

Tags

Next.jsproject structureReactfrontend architectureApp Routerscalabilityteam productivity

V

Velox Studio

AI-Powered Development Studio

Share

Related Articles

Frontend Architecture & Best Practices

Your Next.js App Does Not Have a Performance Problem. It Has a Data Fetching Problem.

Most Next.js performance issues trace back to one root cause: nobody decided how data fetching would work before the first page was built. Here is the strategy we define at the start of every project.

7 min readRead Article
Frontend Architecture & Best Practices

Your React Codebase Should Not Become Unmanageable After 50 Components.

Most React projects fall apart within 6 months. Not because of React, but because nobody planned the architecture before writing the first component. Here is how to fix that from day one.

8 min readRead Article
Startup & MVP Development

The Tech Stack Decision That Will Either Save or Kill Your MVP

Founders overthink stack decisions or blindly copy what big companies use. Here is how to choose the right stack for your MVP — and why Next.js, Supabase, and Vercel is the right default for most founders right now.

6 min readRead Article