Skip to the solution
MediumFrontend10 min read

How do you organize a large Next.js App Router project (colocation, private folders, route groups)?

391 views

The problem

Question presented to candidate: "Our Next.js app has grown to 200 routes and four teams. Files are everywhere. How would you structure the app directory so it stays navigable, and which Next.js conventions help?"

What a strong answer should cover:

  • Only page and route files make a URL public, so components, tests and helpers can be colocated inside app/ safely.
  • Private folders (_components) opt a folder and everything under it out of routing; %5F creates a real URL segment that starts with an underscore.
  • Route groups ((marketing), (shop)) organize by section or team and attach different layouts without changing the URL.
  • Multiple root layouts via groups cause a full page load when navigating between them; two groups resolving to the same path fail the build.
  • A clear split between route code in app/ and shared domain code (lib/, features/, a server-only data layer), plus an import alias like @/.

Clarifying questions expected:

  • "Do sections need different root layouts (different html, fonts, providers)?" β€” that decides between one root layout and several.
  • "Are teams split by feature or by layer?" β€” it decides whether to group folders by feature or keep shared code outside app/.

Code / implementation expected: Yes β€” a folder tree using route groups and private folders, and the layouts that go with them.

  • nextjs
  • project-structure
  • app-router
  • architecture

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

10 min read Β· 9 sections

Read the code

A feature-first layout for a large App Router project
src/
β”œβ”€β”€ app/                          # routing only: URLs, layouts, loading and error UI
β”‚   β”œβ”€β”€ layout.tsx                # one root layout (html, body, global providers)
β”‚   β”œβ”€β”€ (marketing)/              # route group: not part of the URL
β”‚   β”‚   β”œβ”€β”€ layout.tsx            # marketing nav and footer
β”‚   β”‚   β”œβ”€β”€ page.tsx              # "/"
β”‚   β”‚   └── pricing/page.tsx      # "/pricing"
β”‚   β”œβ”€β”€ (shop)/
β”‚   β”‚   β”œβ”€β”€ layout.tsx            # shop header with cart
β”‚   β”‚   β”œβ”€β”€ _components/          # private folder: never routable
β”‚   β”‚   β”‚   └── CartSummary.tsx
β”‚   β”‚   β”œβ”€β”€ cart/page.tsx         # "/cart"
β”‚   β”‚   └── products/[id]/
β”‚   β”‚       β”œβ”€β”€ page.tsx          # "/products/:id"
β”‚   β”‚       β”œβ”€β”€ ProductGallery.tsx  # colocated, not routable (no page or route file name)
β”‚   β”‚       └── page.test.tsx       # colocated test
β”‚   └── dashboard/
β”‚       β”œβ”€β”€ _lib/queries.ts       # route-local server code
β”‚       β”œβ”€β”€ layout.tsx
β”‚       └── page.tsx              # "/dashboard"
β”œβ”€β”€ features/                     # domain code shared by several routes
β”‚   β”œβ”€β”€ billing/
β”‚   β”‚   β”œβ”€β”€ billing.server.ts     # import "server-only"
β”‚   β”‚   └── InvoiceTable.tsx
β”‚   └── catalog/
β”œβ”€β”€ components/ui/                # design system primitives
└── lib/                          # db client, auth helpers, env parsing

# tsconfig.json: "paths": { "@/*": ["./src/*"] }  so imports read "@/features/billing/..."
Route groups with their own layouts, plus a colocated private component
// app/(shop)/layout.tsx: applies to /cart and /products/*, not to /pricing
import { CartSummary } from "./_components/CartSummary";

export default function ShopLayout({ children }: { children: React.ReactNode }) {
  return (
    <div data-section="shop">
      <header>
        Shop <CartSummary count={3} />
      </header>
      {children}
    </div>
  );
}

// app/(shop)/_components/CartSummary.tsx: private folder, so even a page.tsx here is ignored
export function CartSummary({ count }: { count: number }) {
  return <span>{count} items in cart</span>;
}

// app/(marketing)/layout.tsx: a different layout for the same URL level
export default function MarketingLayout({ children }: { children: React.ReactNode }) {
  return (
    <div data-section="marketing">
      <nav>Marketing nav</nav>
      {children}
    </div>
  );
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 79 of 129 in Next.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Next.js questions
Up nextMedium

How do you pass data from Server Components to Client Components in Next.js?

Continue