We use cookies to enhance your experience on the site
CodeWorlds

Directory and File Structure in Next.js (app/ vs pages/)

In Quantum Metropolis, engineers design advanced transportation systems that can operate both on classic roads and on new quantum teleportation routes. Similarly, Next.js offers two parallel routing systems: the traditional Pages Router and the modern App Router. In this module, we will thoroughly analyze the differences between these two approaches and understand how to organize files and directories in our projects.

Two Routing Philosophies in Next.js

Next.js originally relied on a routing convention based on directories in the

pages/
folder. In Next.js 13, a new routing system based on the
app/
folder was introduced. This evolution resembles the transition from classic transportation systems to quantum teleportation in Quantum Metropolis - the new technology is more advanced, but both still coexist.

Pages Router: The Classic Approach

The Pages Router is the original routing system in Next.js. It is simple, well-documented, and still widely used. In this convention, every JavaScript or TypeScript file in the

pages/
directory automatically becomes a route in the application.

1pages/
2β”œβ”€β”€ index.js                # Route: /
3β”œβ”€β”€ about.js                # Route: /about
4β”œβ”€β”€ contact.tsx             # Route: /contact
5β”œβ”€β”€ products/
6β”‚   β”œβ”€β”€ index.js            # Route: /products
7β”‚   └── [id].js             # Route: /products/:id (dynamic)
8└── api/
9    └── hello.js            # API Endpoint: /api/hello

In this system:

  • Files directly map to URL paths
  • Dynamic path segments are marked with square brackets
    [param]
  • A special
    api/
    directory is used for handling API endpoints
  • Each file exports a React component or API handler function by default

App Router: The Modern Approach

The App Router, introduced in Next.js 13 and refined in version 15, represents a new approach based on React Server Components. It is like transitioning from traditional vehicles to quantum teleportation - a fundamental change in how we think about web applications.

1app/
2β”œβ”€β”€ layout.tsx              # Main layout (required)
3β”œβ”€β”€ page.tsx                # Route: /
4β”œβ”€β”€ about/
5β”‚   └── page.tsx            # Route: /about
6β”œβ”€β”€ blog/
7β”‚   β”œβ”€β”€ page.tsx            # Route: /blog
8β”‚   β”œβ”€β”€ layout.tsx          # Layout specific to /blog and its sub-routes
9β”‚   └── [slug]/
10β”‚       └── page.tsx        # Route: /blog/:slug (dynamic)
11└── api/
12    └── users/
13        └── route.ts        # API Endpoint: /api/users

In the App Router:

  • Only directories define routes, and special files within those directories serve specific functions
  • The
    page.tsx
    file renders the page component for a given route
  • The
    layout.tsx
    file defines a shared layout for all pages in the directory and its subdirectories
  • Dynamic segments still use square brackets:
    [param]
  • API endpoints are created using
    route.ts
    files

File Naming and Conventions in App Router

The App Router introduces "convention over configuration" with a series of special files that have specific roles. This is like specialized modules in Quantum Metropolis systems - each with a defined function in the overall architecture.

Basic Special Files:

  1. page.tsx: Renders the route's user interface and makes the path publicly accessible.

    1// app/dashboard/page.tsx
    2export default function DashboardPage() {
    3  return (
    4    <section>
    5      <h1>Quantum Metropolis Control Panel</h1>
    6      <p>Welcome to the command center!</p>
    7    </section>
    8  );
    9}
  2. layout.tsx: Defines a shared interface for a segment and its children.

    1// app/dashboard/layout.tsx
    2export default function DashboardLayout({
    3  children,
    4}: {
    5  children: React.ReactNode;
    6}) {
    7  return (
    8    <div className="dashboard-layout">
    9      <nav className="dashboard-sidebar">
    10        {/* Sidebar navigation */}
    11      </nav>
    12      <main className="dashboard-main">{children}</main>
    13    </div>
    14  );
    15}
  3. route.ts: Creates an API endpoint using the new request handling API.

    1// app/api/status/route.ts
    2import { NextResponse } from 'next/server';
    3
    4export async function GET() {
    5  return NextResponse.json({
    6    status: 'online',
    7    systemsOperational: 15,
    8    time: new Date().toISOString()
    9  });
    10}
    11
    12export async function POST(request: Request) {
    13  const data = await request.json();
    14  // Processing data...
    15  return NextResponse.json({ success: true });
    16}
  4. loading.tsx: Creates a loading interface with React Suspense.

    1// app/dashboard/loading.tsx
    2export default function DashboardLoading() {
    3  return (
    4    <div className="dashboard-loading">
    5      <div className="spinner"></div>
    6      <p>Loading Metropolis system data...</p>
    7    </div>
    8  );
    9}
  5. error.tsx: Creates an error handling interface with React Error Boundary.

    1'use client'; // Error handling components must be client components
    2
    3import { useEffect } from 'react';
    4
    5export default function DashboardError({
    6  error,
    7  reset,
    8}: {
    9  error: Error;
    10  reset: () => void;
    11}) {
    12  useEffect(() => {
    13    // Log error to monitoring system
    14    console.error(error);
    15  }, [error]);
    16
    17  return (
    18    <div className="error-container">
    19      <h2>An error occurred in the Metropolis systems!</h2>
    20      <p>{error.message}</p>
    21      <button onClick={reset}>Try again</button>
    22    </div>
    23  );
    24}
  6. not-found.tsx: Creates an interface for a not-found resource (404).

    1// app/not-found.tsx
    2export default function NotFound() {
    3  return (
    4    <div className="not-found">
    5      <h2>Unknown Zone</h2>
    6      <p>No Quantum Metropolis module was found with that identifier.</p>
    7      <a href="/">Return to command center</a>
    8    </div>
    9  );
    10}

Advanced App Router Features

The App Router introduces several advanced organizational features that resemble the advanced transportation systems in Quantum Metropolis - they allow for more complex and flexible routes.

1. Route Groups

Route groups allow organizing files without affecting the URL structure. They are marked by enclosing the directory name in parentheses:

(group-name)
.

1app/
2β”œβ”€β”€ (marketing)/        # Route group (does not affect URL path)
3β”‚   β”œβ”€β”€ about/
4β”‚   β”‚   └── page.tsx    # Route: /about
5β”‚   └── contact/
6β”‚       └── page.tsx    # Route: /contact
7└── (dashboard)/        # Another route group
8    β”œβ”€β”€ layout.tsx      # Shared layout for dashboard routes
9    β”œβ”€β”€ page.tsx        # Route: /
10    └── stats/
11        └── page.tsx    # Route: /stats

Route groups are like districts in Quantum Metropolis - they help organize the city structure, but don't necessarily affect addresses.

2. Parallel Routes

Parallel routes allow simultaneous rendering of multiple pages in the same view, which is useful for complex layouts. They are marked by a slot name with the "@" sign:

@slot-name
.

1app/
2β”œβ”€β”€ layout.tsx          # Main layout
3β”œβ”€β”€ page.tsx            # Main page
4└── dashboard/
5    β”œβ”€β”€ layout.tsx      # Layout with multiple slots
6    β”‚   // layout.tsx defines slots {children, @stats, @notifications}
7    β”œβ”€β”€ page.tsx        # Default content for the main slot
8    β”œβ”€β”€ @stats/
9    β”‚   └── page.tsx    # Content for the @stats slot
10    └── @notifications/
11        └── page.tsx    # Content for the @notifications slot
1// app/dashboard/layout.tsx
2export default function DashboardLayout({
3  children,
4  stats,
5  notifications,
6}: {
7  children: React.ReactNode;
8  stats: React.ReactNode;
9  notifications: React.ReactNode;
10}) {
11  return (
12    <div className="dashboard-grid">
13      <main>{children}</main>
14      <aside className="stats-panel">{stats}</aside>
15      <section className="notifications-panel">{notifications}</section>
16    </div>
17  );
18}

Parallel routes are like parallel dimensions in Quantum Metropolis - different realities accessible simultaneously.

3. Intercepting Routes

These allow "intercepting" a route and displaying it differently, which is ideal for modals that preserve navigation context. They are marked by the segment name with the sign "(.)":

(.)name
,
(..)name
or
(...)name
.

1app/
2β”œβ”€β”€ layout.tsx
3β”œβ”€β”€ page.tsx            # Route: /
4└── dashboard/
5    β”œβ”€β”€ layout.tsx
6    β”œβ”€β”€ page.tsx        # Route: /dashboard
7    └── projects/
8        β”œβ”€β”€ page.tsx    # Route: /dashboard/projects
9        β”œβ”€β”€ [id]/
10        β”‚   └── page.tsx # Route: /dashboard/projects/:id
11        └── (.)create/  # Intercepts /dashboard/projects/create
12            └── page.tsx # Displayed as a modal over /dashboard/projects

Intercepting routes are like quantum shortcuts in Quantum Metropolis - they allow quick access to resources without changing context.

Code Organization in Next.js 15

Besides the routing structure, Next.js 15 suggests specific ways of organizing code. This is like planning districts in Quantum Metropolis - proper organization increases efficiency and readability.

Shared Components and Resources

Components used in multiple places should be organized consistently. Here is the recommended structure:

1src/
2β”œβ”€β”€ app/                 # Application with App Router
3β”‚   β”œβ”€β”€ layout.tsx
4β”‚   └── page.tsx
5β”œβ”€β”€ components/          # Shared components
6β”‚   β”œβ”€β”€ ui/              # Basic UI components
7β”‚   β”‚   β”œβ”€β”€ Button.tsx
8β”‚   β”‚   └── Card.tsx
9β”‚   └── features/        # Feature-specific components
10β”‚       β”œβ”€β”€ dashboard/
11β”‚       └── auth/
12β”œβ”€β”€ lib/                 # Shared functions and classes
13β”‚   β”œβ”€β”€ api/
14β”‚   └── utils/
15β”œβ”€β”€ hooks/               # Custom React hooks
16β”‚   β”œβ”€β”€ useAuth.ts
17β”‚   └── useLocalStorage.ts
18└── styles/              # Global styles
19    └── globals.css

Co-location vs. Centralization

Next.js 15 and the App Router support a "co-location" approach, where files related to a specific route are placed close together. This is like micro-districts in Quantum Metropolis - everything you need is nearby.

1app/
2└── dashboard/
3    β”œβ”€β”€ page.tsx
4    β”œβ”€β”€ loading.tsx       # Loading for this route
5    β”œβ”€β”€ error.tsx         # Error handling for this route
6    β”œβ”€β”€ layout.tsx        # Layout for this route
7    β”œβ”€β”€ actions.ts        # Server Actions for this route
8    β”œβ”€β”€ components/       # Components specific to this route
9    β”‚   β”œβ”€β”€ StatusCard.tsx
10    β”‚   └── DashboardNav.tsx
11    └── styles/           # Styles specific to this route
12        └── dashboard.module.css

Comparing App Router and Pages Router

Now that we've explored both routing systems, let's compare them - just as Quantum Metropolis engineers might compare classic transport with quantum teleportation.

Rendering

  1. Pages Router:

    • Client-side rendering by default
    • SSR and SSG available through getServerSideProps and getStaticProps
    • Relatively simple data models
  2. App Router:

    • Server Components by default
    • Choice between Server and Client Components
    • More advanced data flow model
    • Streaming rendering

Loading State

  1. Pages Router:

    • Manual loading state management
    • Often uses external libraries
  2. App Router:

    • Built-in loading.tsx components
    • Integration with React Suspense
    • Automatic loading indicators

Error Handling

  1. Pages Router:

    • Manual error management
    • General component for 500 errors
  2. App Router:

    • error.tsx components with granular error handling
    • Integration with React Error Boundary
    • Ability to recover/retry after an error

Layouts

  1. Pages Router:

    • Manual layout creation with _app.js component
    • One layout for the entire application
    • Nested layouts require additional work
  2. App Router:

    • Built-in layout system through layout.tsx
    • Ability to create nested layouts
    • Layout sharing is simple and intuitive

When to Use Which Routing System?

Choosing between App Router and Pages Router resembles choosing between quantum teleportation and traditional transport in Quantum Metropolis - it depends on specific needs.

Use App Router when:

  • You are creating a new project from scratch
  • You want to use the latest React and Next.js features
  • You need advanced layout and organization capabilities
  • You care about better performance and streaming rendering
  • You want to use React Server Components

Use Pages Router when:

  • You have an existing project based on Pages Router
  • You need a simpler mental model
  • You use libraries that are not yet compatible with the App Router
  • You want to use ready-made patterns and solutions

Migration from Pages Router to App Router

If you have an existing project using Pages Router, you can gradually migrate to App Router. This is like modernizing districts in Quantum Metropolis - you don't have to rebuild everything at once.

Next.js 15 allows both systems to coexist. You can start using

app/
for new features while keeping existing pages in
pages/
.

1.
2β”œβ”€β”€ app/                # New features using App Router
3β”‚   β”œβ”€β”€ new-feature/
4β”‚   β”‚   └── page.tsx    # /new-feature
5β”‚   └── layout.tsx
6└── pages/              # Existing pages using Pages Router
7    β”œβ”€β”€ index.js        # /
8    └── about.js        # /about

Important notes about migration:

  • Routes in
    app/
    take priority over routes in
    pages/
  • Both structures use the same Next.js configuration
  • You can gradually move pages from
    pages/
    to
    app/

Best Practices for File Organization

Regardless of the chosen routing system, it's worth following certain best practices:

  1. Feature-oriented structure: Organize components and code around business features, not by technical types.

    1src/
    2β”œβ”€β”€ features/
    3β”‚   β”œβ”€β”€ auth/
    4β”‚   β”‚   β”œβ”€β”€ components/
    5β”‚   β”‚   β”œβ”€β”€ hooks/
    6β”‚   β”‚   └── utils/
    7β”‚   └── dashboard/
    8β”‚       β”œβ”€β”€ components/
    9β”‚       β”œβ”€β”€ hooks/
    10β”‚       └── utils/
  2. Modular approach: Treat each feature as an isolated module with a clearly defined API.

  3. Consistent naming conventions: Establish conventions and stick to them throughout the project.

    • Component files: PascalCase (Button.tsx)
    • Hooks: camelCase with "use" prefix (useAuth.ts)
    • Helper functions: camelCase (formatDate.ts)
  4. Relative vs. absolute imports: Use import aliases (

    @/
    ) for better readability and easier file moving.

  5. Test co-location: Place tests close to the tested components.

    1src/
    2β”œβ”€β”€ components/
    3β”‚   β”œβ”€β”€ Button.tsx
    4β”‚   └── Button.test.tsx

Summary

The directory and file structure in Next.js 15 is like the architecture of Quantum Metropolis - with proper planning, it can be both elegant and functional. The App Router introduces a more advanced but also more flexible application organization model, while the Pages Router offers a simpler and more direct approach.

The choice between them depends on the specific needs of the project, but for new projects, it is recommended to use the App Router to take advantage of the latest features and optimizations. Remember that good project organization is the key to easy maintenance and extensibility in the future.

In the next module, we will dive into the Next.js 15 component system, particularly the differences between Client Components and Server Components, which are a fundamental part of the App Router.

Go to CodeWorlds→