Next.js File Structure

Organize Next.js projects for maintainability and performance.

TL;DR

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

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
    

Tips

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

Warnings

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

FAQ