The App Router isn't just a new way to organize files; it's a fundamental shift in how we build React applications, built around React Server Components (RSCs).

Server Components by Default

In the app directory, every component you create is a React Server Component by default.

  • What this means:
  • The component's code never ships to the browser. It only ever runs on the server.
  • This results in a zero client-side JavaScript bundle size for that component.
  • You can use async/await directly within the component to fetch data, access databases, or read from the filesystem, as if you were in a Node.js environment.

JavaScript


// This is a Server Component. Its code is not sent to the client.
async function getPost(slug) {
  const res = await fetch(`https://api.example.com/posts/${slug}`);
  return res.json();
}

export default async function Page({ params }) {
  const post = await getPost(params.slug);
  return <div>{post.content}</div>;
}

The "use client" Boundary

So how do you create interactivity? You can't use useState, useEffect, or onClick in a Server Component because they require JavaScript in the browser.

To use these client-side features, you must create a "Client Component" by placing the "use client" directive at the very top of the file.

JavaScript


"use client"; // This marks the file as a boundary.

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      You clicked {count} times
    </button>
  );
}

Key Concept: The "use client" directive defines the "boundary" between the server and client. Any component you import into a Client Component also becomes part of the client bundle. A good strategy is to keep your Client Components as small and isolated as possible (i.e., push them down the component tree).

Special Files: Building Layouts with Ease

The App Router introduces special, file-convention-based files to handle common UI patterns.

  • layout.js: Defines a UI that is shared across multiple routes. Layouts preserve state and do not re-render on navigation. Perfect for a root layout with a header, footer, or persistent sidebar.
  • template.js: Similar to a layout, but it creates a new instance for each child on navigation. State is not preserved. Useful for implementing enter/exit animations.
  • loading.js: Creates an instant loading UI. Next.js will automatically wrap your page.js in a React Suspense Boundary and show your loading.js component while the page's data is being fetched. This enables instant feedback and progressive rendering (streaming).
  • error.js: Creates an error UI. It automatically wraps your page in a React Error Boundary, catching any errors and displaying your error.js component instead of crashing the app.

New Data Fetching Patterns

The native fetch API is now the primary way to fetch data in Server Components, and Next.js extends it with powerful caching and revalidation controls.

JavaScript


// This fetch request will be cached indefinitely by default (like SSG)
fetch('https://...');

// This fetch will be re-fetched at most every 60 seconds (ISR)
fetch('https://...', { next: { revalidate: 60 } });

// This fetch will never be cached and will be run on every request (SSR)
fetch('https://...', { cache: 'no-store' });

// This fetch can be revalidated on-demand using a tag
fetch('https://...', { next: { tags: ['products'] } });

This new model unifies SSG, SSR, and ISR into a single, flexible API.