Next.js App Router

Build routes, nested layouts, and loading states with file-based routing in the Next.js App Router.

TL;DR

  1. Create a route by adding a page.tsx file inside a folder.
  2. Share UI across routes by adding a layout.tsx at any level.
  3. Await the params and searchParams props, because both are promises.

Routes From Folders

    page.tsx

    Add a page.tsx file to a folder to turn that folder path into a route.

    // app/blog/page.tsx -> /blog
    export default function Page() {
      return <h1>Blog</h1>;
    }
    Nested Routes

    Nest folders to build longer paths; each folder adds one segment to the URL.

    app/
      blog/
        page.tsx          // /blog
        drafts/
          page.tsx        // /blog/drafts
    searchParams

    Read the query string in a page by awaiting the searchParams prop.

    export default async function Page({
      searchParams,
    }: {
      searchParams: Promise<{ q?: string }>;
    }) {
      const { q } = await searchParams;
      return <p>Results for {q}</p>;
    }

Layouts

    layout.tsx

    Wrap a route and all its children in shared UI that stays mounted.

    // app/dashboard/layout.tsx
    export default function Layout({
      children,
    }: {
      children: React.ReactNode;
    }) {
      return <section>{children}</section>;
    }
    Nested Layouts

    Layouts stack, so a page renders inside every layout above it in the tree.

    app/
      layout.tsx          // html, body
      dashboard/
        layout.tsx        // sidebar
        page.tsx          // content
    template.tsx

    Use a template when the wrapper must reset its state on every navigation.

    // app/dashboard/template.tsx
    export default function Template({
      children,
    }: {
      children: React.ReactNode;
    }) {
      return <div className="fade-in">{children}</div>;
    }

Special Files

    loading.tsx

    Show instant fallback UI while the page for this segment is loading.

    // app/dashboard/loading.tsx
    export default function Loading() {
      return <p>Loading dashboard...</p>;
    }
    error.tsx

    Catch errors thrown in this segment and render a fallback in their place.

    'use client';
    
    export default function Error({
      error,
    }: {
      error: Error;
    }) {
      return <p>Something went wrong.</p>;
    }
    not-found.tsx

    Render custom 404 UI when a route calls notFound() or does not exist.

    // app/not-found.tsx
    export default function NotFound() {
      return <h1>Page not found</h1>;
    }

Advanced Routing

    (group)

    Group routes in a parenthesized folder to give them their own layout.

    app/
      (marketing)/
        layout.tsx        // marketing shell
        page.tsx          // /
      (app)/
        layout.tsx        // app shell
        dashboard/page.tsx
    @slot

    Render several pages in one layout by passing each slot in as a prop.

    // app/layout.tsx with app/@modal/
    export default function Layout({
      children,
      modal,
    }: {
      children: React.ReactNode;
      modal: React.ReactNode;
    }) {
      return <>{children}{modal}</>;
    }
    default.tsx

    Give every slot a fallback, which Next.js 16 requires at build time.

    // app/@modal/default.tsx
    export default function Default() {
      return null;
    }

Tips

  1. Put shared navigation in a layout.tsx, because layouts keep their state and do not re-render when you move between child pages.
  2. Add a loading.tsx to slow routes so users see a fallback immediately instead of waiting on a blank screen.

Warnings

  1. Reading params or searchParams without await breaks in Next.js 16, because synchronous access was removed.
  2. Every parallel route slot needs a default.tsx file in Next.js 16, or the production build fails.

In Practice

FAQ