Next.js File Structure

Organize Next.js projects for maintainability and performance.

TL;DR

  1. 01Use app router for file-based routing with folders.
  2. 02Group related files with parentheses to exclude from routing.
  3. 03Colocate components, hooks, and utilities near where they're used.

Tips

  1. 01Organize by feature or domain first, not by file type — this keeps related code together and makes refactoring easier.

Warnings

  1. 01Don't create too many deeply nested folder levels — keep structures 3–4 levels deep for clarity.

App Router Basics

  • Create routes by adding a folder and a page.tsx file inside it.

    app/
      page.tsx              # / route
      about/
        page.tsx            # /about route
      blog/
        page.tsx            # /blog route
        [slug]/
          page.tsx          # /blog/:slug route
    
  • Map URL path segments directly to folder names in the app directory.

    app/users/[id]/posts/page.tsx  →  /users/:id/posts
    
  • Add a root layout.tsx to wrap every page with shared HTML structure.

    // app/layout.tsx
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return <html lang="en"><body>{children}</body></html>;
    }
    
  • Use page.tsx for visible UI and route.ts for API route handlers.

    app/
      api/
        users/
          route.ts    # GET /api/users, POST /api/users
      users/
        page.tsx      # /users page rendered in the browser
    
  • Add a loading.tsx next to any page to show a Suspense fallback.

    // app/blog/loading.tsx
    export default function Loading() {
      return <p>Loading posts...</p>;
    }
    
  • Use dynamic [slug] segments to match variable URL parts — in Next.js 15, params is a Promise.

    // app/blog/[slug]/page.tsx
    export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
      const { slug } = await params;
      return <h1>Post: {slug}</h1>;
    }
    

Special Files

  • Use layout.tsx to share persistent UI across child routes without remounting.

    // app/layout.tsx
    export default function RootLayout({ children }: { children: React.ReactNode }) {
      return (
        <html>
          <body>
            <Header />
            {children}
            <Footer />
          </body>
        </html>
      );
    }
    
  • Use error.tsx as an error boundary — it must be a Client Component.

    // app/blog/error.tsx
    "use client";
    export default function Error({ error, reset }: { error: Error; reset: () => void }) {
      return (
        <div>
          <h2>Something went wrong</h2>
          <button onClick={reset}>Try again</button>
        </div>
      );
    }
    
  • Use loading.tsx to create an automatic Suspense boundary for the segment.

    // app/blog/loading.tsx
    export default function Loading() {
      return <p>Loading posts...</p>;
    }
    
  • Use not-found.tsx to render a custom 404 for each route segment.

    // app/blog/[slug]/not-found.tsx
    export default function NotFound() {
      return <h1>Post not found</h1>;
    }
    
  • Use template.tsx when the layout must remount on each navigation.

    // app/dashboard/template.tsx
    export default function Template({ children }: { children: React.ReactNode }) {
      // Re-runs useEffect and re-mounts on every route change
      return <div>{children}</div>;
    }
    

Route Groups

  • Wrap folder names in parentheses to exclude them from the URL path.

    app/
      (marketing)/
        page.tsx          # / (still at root URL)
        about/page.tsx    # /about
        contact/page.tsx  # /contact
      (dashboard)/
        layout.tsx        # separate layout, no URL impact
        page.tsx          # /dashboard
    
  • Give each route group its own layout for auth vs app separation.

    app/
      (auth)/
        layout.tsx        # auth layout with centered card
        login/page.tsx    # /login
        signup/page.tsx   # /signup
      (app)/
        layout.tsx        # app layout with sidebar nav
        dashboard/page.tsx
    
  • Scope middleware-like access control by grouping protected routes.

    app/
      (protected)/
        dashboard/page.tsx
        settings/page.tsx
      (public)/
        page.tsx
        about/page.tsx
    
  • Define a layout per group without affecting the URL structure.

    // app/(auth)/layout.tsx
    export default function AuthLayout({ children }: { children: React.ReactNode }) {
      return <main className="auth-bg">{children}</main>;
    }
    
  • Combine route groups with parallel routes using the @ prefix.

    app/
      (dashboard)/
        @analytics/page.tsx
        @overview/page.tsx
        layout.tsx
    

Module and Export Patterns

  • Use TypeScript path aliases to avoid deep relative imports across the project.

    // tsconfig.json
    {
      "compilerOptions": {
        "paths": { "@/*": ["./src/*"] }
      }
    }
    
  • Create barrel files with index.ts to expose a clean public API for each feature.

    // features/auth/index.ts
    export { LoginForm } from "./components/LoginForm";
    export { useAuth } from "./hooks/useAuth";
    export type { AuthUser } from "./types";
    
  • Mark server-only modules with the server-only package to prevent client imports.

    // lib/db.ts
    import "server-only";
    export const db = new PrismaClient();
    
  • Use a global types/ folder for shared TypeScript interfaces across features.

    types/
      api.ts      # shared API response shapes
      user.ts     # User interface used app-wide
      post.ts     # Post interface used app-wide
    
  • Re-export shared UI primitives from a single index to keep import paths short.

    app/
      ui/
        Button.tsx
        Card.tsx
        Modal.tsx
        index.ts    # export { Button, Card, Modal }
    

Scaling Patterns

  • Use feature folders to group all files for one domain in one place.

    app/
      features/
        auth/
          page.tsx
          components/
          hooks/
          utils.ts
        posts/
          page.tsx
          components/
          hooks/
    
  • Keep all Next.js special files inside the feature folder.

    features/users/
      page.tsx
      layout.tsx
      error.tsx
      loading.tsx
      components/
        UserCard.tsx
      hooks/
        useUser.ts
    
  • Create barrel files with index.ts to simplify imports from feature folders.

    // features/auth/index.ts
    export { LoginForm } from "./components/LoginForm";
    export { useAuth } from "./hooks/useAuth";
    
  • Place server actions in a dedicated actions/ folder inside each feature.

    features/posts/
      actions/
        createPost.ts
        deletePost.ts
      components/
      page.tsx
    
  • Put cross-feature utilities in a root lib/ folder accessible to all routes.

    lib/
      format.ts      # shared date/number formatting
      fetcher.ts     # shared fetch wrapper
      constants.ts   # app-wide constants
    

FAQ