Next.js App Router Patterns

Comprehensive patterns for Next.js 14+ App Router architecture, Server Components, and modern full-stack React development

What Is This

The Next.js App Router Patterns skill provides a comprehensive set of architectural and implementation patterns for the Next.js App Router, introduced in Next.js 13 and recommended for all new projects as of Next.js 14+. This skill covers the core building blocks of modern full-stack React applications using Next.js, with a focus on Server Components, advanced routing techniques, streaming, and optimized data fetching. By mastering these patterns, developers can build scalable, performant, and maintainable applications using the latest Next.js capabilities.

Why Use It

Next.js has evolved rapidly, and the App Router introduces significant changes compared to the legacy Pages Router. Modern requirements such as server-rendered React components, fine-grained data fetching, and complex routing are now first-class features. Using the patterns outlined in this skill ensures that your application:

  • Leverages Server Components for better performance and security.
  • Uses streaming and advanced data fetching strategies for fast, seamless user experiences.
  • Employs parallel and intercepting routes to handle complex navigation scenarios.
  • Is structured for maintainability and scalability, following Next.js conventions.

Adopting these patterns is critical for teams aiming to build production-grade, modern web applications with Next.js.

How to Use It

Rendering Modes

Understanding the rendering modes is foundational:

ModeWhereWhen to Use
Server ComponentsServer onlyData fetching, heavy computation, secrets
Client ComponentsBrowserInteractivity, hooks, browser APIs
StaticBuild timeContent that rarely changes
DynamicRequest timePersonalized or real-time data
StreamingProgressiveLarge pages, slow data sources

Example: Server vs Client Component

// Server Component (app/page.tsx)
export default async function Page() {
  const data = await fetchDataFromAPI();
  return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

// Client Component (app/components/Counter.tsx)
'use client'
import { useState } from 'react';
export default function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

File Conventions

The App Router relies on a clear directory structure in the app/ directory:

app/
├── layout.tsx       # Shared UI wrapper for all children routes
├── page.tsx         # Main component for the route
├── loading.tsx      # Loading UI for suspense/streaming
├── error.tsx        # Error boundary for the route
├── not-found.tsx    # 404 handler
├── [id]/            # Dynamic segment
├── (group)/         # Route grouping (optional)

Key Patterns:

  • Use layout.tsx for shared UI across nested routes.
  • Place route-specific UI in page.tsx.
  • Use loading.tsx for progressive loading states, particularly with streaming.

Parallel and Intercepting Routes

Parallel and intercepting routes are essential for building complex UIs like modals or dashboards.

Parallel Routes Example:

app/
├── dashboard/
│   ├── @main/page.tsx          # Main dashboard view
│   └── @modal/(...)            # Parallel modal route

Intercepting Routes Example:

Use (.) notation to intercept navigation and render a modal over the current page.

// app/@modal/(.)profile/[id]/page.tsx
export default function ProfileModal({ params }) {
  return <Modal>User Profile: {params.id}</Modal>;
}

Data Fetching and Caching

Server Components allow fetching data directly in component code. Use the built-in fetch API with caching strategies.

Example:

export default async function Page() {
  const res = await fetch('https://api.example.com/data', { cache: 'no-store' });
  const data = await res.json();
  return <div>{data.value}</div>;
}

For static generation, use fetch with default caching. For dynamic data, use { cache: 'no-store' } or { next: { revalidate: seconds } }.

Server Actions

Server Actions enable full-stack features like form submissions without API routes.

Example:

// app/actions.ts
'use server'
export async function createNote(formData) {
  // Handle form submission on the server
}

// app/page.tsx
import { createNote } from './actions';
export default function Page() {
  return (
    <form action={createNote}>
      <input name="note" type="text" />
      <button type="submit">Add Note</button>
    </form>
  );
}

When to Use It

  • When building new applications using Next.js 13+ with the App Router.
  • When migrating projects from the legacy Pages Router to the App Router.
  • When implementing advanced features such as Server Components, streaming, or full-stack form handling.
  • When optimizing data fetching and caching for performance and scalability.
  • When developing complex navigation flows involving modals or parallel routes.

Important Notes

  • The App Router is the recommended routing mechanism for all new Next.js projects.
  • Server Components cannot use React state or browser APIs. Use Client Components for interactive features.
  • Streaming enables faster time-to-first-byte, especially for data-heavy or slow-loading pages.
  • Organize your app/ directory using Next.js file conventions for maintainability.
  • Use Server Actions for full-stack forms and mutations without defining separate API endpoints.
  • Parallel and intercepting routes require careful planning of route structure and grouping.
  • Always consult the Next.js documentation for the latest best practices, as the framework evolves quickly.

By mastering these patterns, you will be equipped to design and build robust, modern web applications using the latest features of Next.js.