Next.js & the App Router

Reviewed & published by Brayan K

Next.js is the most popular React framework — it wraps React with file-based routing, server rendering, data fetching, and production optimizations. The App Router (the app/ directory) is its modern routing system, built on React Server Components, with nested layouts and colocated loading and error UI. In this lesson you'll learn how folders become routes, what the special files (page, layout, loading, error) do, and where server and client components fit.

Part of the free React course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

1️⃣ File-Based Routing

There's no router config. Your app/ folder tree is the route tree. A folder with a page.tsx becomes a navigable URL; nesting folders nests URLs.

app/
├─ page.tsx              ->  /
├─ about/
│  └─ page.tsx           ->  /about
├─ blog/
│  ├─ page.tsx           ->  /blog
│  └─ [slug]/
│     └─ page.tsx        ->  /blog/:slug   (dynamic)
└─ shop/
   └─ [id]/
      └─ reviews/
         └─ page.tsx     ->  /shop/:id/reviews

The demo models exactly this mapping — feed it a file path, get the URL it serves:

// In the App Router, your folder tree IS your routes. We model the mapping
// from a file path under app/ to the URL it serves.

function fileToRoute(filePath) {
  // strip the leading "app" and the trailing "/page.tsx"
  let route = filePath
    .replace(/^app/, "")
    .replace(/\/page\.tsx$/, "");
  // [slug] folders are dynamic segments -> :slug
  route = route.replace(/\[(\w+)\]/g, ":$1");
  return route === "" ? "/" : route;
}

console.log(fileToRoute("app/page.tsx"));                 // /
console.log(fileToRoute("app/blog/page.tsx"));            // /blog
console.log(fileToRoute("app/blog/[slug]/page.tsx"));     // /blog/:slug
console.log(fileToRoute("app/shop/[id]/reviews/page.tsx")); // /shop/:id/reviews
// Folders -> URL segments. [param] -> dynamic. No route config anywhere.

2️⃣ A Page and a Dynamic Route

A page is a default-exported component. For a dynamic segment like [slug], Next.js passes the matched value in params. Because pages are server components, you can await data right inside them — no useEffect.

// app/blog/[slug]/page.tsx  ->  /blog/hello-world
export default async function BlogPost({ params }) {
  const { slug } = await params;                 // the dynamic segment
  const post = await getPost(slug);              // fetch on the server
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </article>
  );
}

// Pre-render known slugs at build time (static generation):
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map((p) => ({ slug: p.slug }));
}

3️⃣ Special Files

The App Router gives each route segment a set of reserved filenames. Drop one in a folder and Next.js wires it up automatically — loading and error UI come for free.

FileWhat it renders
page.tsxthe unique UI for that route
layout.tsxshared shell wrapping the page and its children
loading.tsxinstant fallback while the segment's data loads (Suspense)
error.tsxerror boundary UI when the segment throws
not-found.tsxUI for a 404 in that segment

Match each special file to its job in the demo:

// 🎯 YOUR TURN — fill in each ___ then press "Try it Yourself".
// The App Router uses SPECIAL filenames for each piece of a route. Match
// the file to what it renders.

const purpose = {
  // 1) the actual page UI for a route
  "page.tsx": "___",      // 👉 "the page UI"
  // 2) shared shell wrapping a route and its children (nav, etc.)
  "layout.tsx": "___",    // 👉 "the layout shell"
  // 3) shown instantly while the page's data is loading
  "loading.tsx": "the loading UI",
  // 4) shown when a route segment throws
  "error.tsx": "the error UI",
};

console.log("page.tsx   ->", purpose["page.tsx"]);
console.log("layout.tsx ->", purpose["layout.tsx"]);
console.log("loading.tsx->", purpose["loading.tsx"]);
console.log("error.tsx  ->", purpose["error.tsx"]);

// ✅ Expected output:
//    page.tsx   -> the page UI
//    layout.tsx -> the layout shell
//    loading.tsx-> the loading UI
//    error.tsx  -> the error UI

4️⃣ Nested Layouts

A layout.tsx wraps everything below it and — crucially — stays mounted as you navigate between sibling pages. The root layout renders <html> and <body>; nested layouts add section-specific chrome like a dashboard sidebar.

// app/layout.tsx — the root layout (required)
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <header><nav>...</nav></header>
        {children}             
      </body>
    </html>
  );
}

// app/dashboard/layout.tsx — wraps only /dashboard/* pages
export default function DashboardLayout({ children }) {
  return (
    <div className="flex">
      <Sidebar />             
      <main>{children}</main>
    </div>
  );
}

The demo models how a page is wrapped by its whole layout chain, root first:

// Layouts NEST: a page is wrapped by every layout from the root down to it.
// We model "rendering" a route as composing its layout chain around the page.

function render(layouts, page) {
  // wrap the page with each layout, innermost last
  return layouts.reduceRight(
    (children, layout) => layout + "(" + children + ")",
    page
  );
}

// URL /dashboard/settings is wrapped by RootLayout > DashboardLayout
const layouts = ["RootLayout", "DashboardLayout"];
const page = "SettingsPage";

console.log(render(layouts, page));
// RootLayout(DashboardLayout(SettingsPage))
// The root layout (html/body, nav) stays mounted; only the inner part
// swaps as you navigate between sibling pages — that's nested layouts.

5️⃣ Server vs Client Components

In the App Router, components are server components by default: they render on the server and ship zero JavaScript. When you need interactivity — state, effects, event handlers, browser APIs — add the "use client" directive at the top of the file.

// Server component (default) — runs on the server, no JS shipped
export default async function Page() {
  const data = await db.query("SELECT ...");   // safe: secrets stay server-side
  return <Dashboard data={data} />;
}

// Client component — opt in for interactivity
"use client";
import { useState } from "react";

export default function Counter() {
  const [n, setN] = useState(0);                // hooks need a client component
  return <button onClick={() => setN(n + 1)}>{n}</button>;
}

Reorder Challenge

These lines of a dynamic blog page are scrambled. Put them in the correct order:

A.   const post = await getPost(slug);
B.   export default async function Post({ params }) {
C.   return <article><h1>{post.title}</h1></article>;
D.   const { slug } = await params;
E.   }

Quick Recall

1) What URL does app/products/[id]/page.tsx serve?

/products/:id — a dynamic route. The matched id arrives in params.

2) Are App Router components server or client by default?

Server by default. They render on the server and ship no JS. Add "use client" at the top of the file to opt into interactivity (state, effects, handlers).

3) What's the benefit of a nested layout.tsx over re-rendering chrome on every page?

The layout stays mounted across sibling navigations, so shared UI (a sidebar, its scroll position, its state) persists and only the inner page swaps.

Common Errors (and the fix)

📋 Quick Reference

ConceptExample
Static routeapp/about/page.tsx → /about
Dynamic routeapp/blog/[slug]/page.tsx
Read paramsconst { slug } = await params
Shared shelllayout.tsx with {children}
Loading UIloading.tsx (auto Suspense)
Interactivity"use client" at top of file

Mini-Challenge: Route the Folders

Given a few file paths under app/, log the URL each one serves. Reuse the routing logic — this is how every Next.js URL is decided.

function fileToRoute(filePath) {
  let route = filePath.replace(/^app/, "").replace(/\/page\.tsx$/, "");
  route = route.replace(/\[(\w+)\]/g, ":$1");
  return route === "" ? "/" : route;
}

const paths = [
  "app/page.tsx",
  "app/dashboard/page.tsx",
  "app/users/[id]/page.tsx",
];

// your code here: log fileToRoute(path) for each path

🎉 Lesson Complete

Practice quiz

What is Next.js?

  • A CSS library
  • A replacement for JavaScript
  • A React framework adding routing, server rendering, data fetching, and optimizations
  • A database

Answer: A React framework adding routing, server rendering, data fetching, and optimizations. Next.js is a full framework built around React, with file-based routing, SSR/SSG, and production optimizations.

What is the App Router?

  • Next.js's modern routing system in the app/ directory, built on React Server Components
  • A third-party routing library
  • The old pages/ system
  • A CSS router

Answer: Next.js's modern routing system in the app/ directory, built on React Server Components. The App Router uses the app/ directory, server components by default, nested layouts, and colocated UI files.

How does file-based routing decide URLs in the App Router?

  • From a routes.js config file
  • From a database
  • From the component names
  • The folder structure under app/ is the route tree; a folder with page.tsx becomes a URL

Answer: The folder structure under app/ is the route tree; a folder with page.tsx becomes a URL. Folders under app/ map to URL segments; you create folders and page files instead of registering routes.

What does a folder named [slug] under app/ create?

  • A static route
  • A dynamic route segment whose value arrives in params
  • A layout
  • An error page

Answer: A dynamic route segment whose value arrives in params. [slug] is a dynamic segment; the matched value is passed to the page in params.

Which special file renders the unique UI for a route?

  • page.tsx
  • layout.tsx
  • loading.tsx
  • error.tsx

Answer: page.tsx. page.tsx renders the unique UI for that route segment and makes the folder navigable.

What does layout.tsx do?

  • Renders a 404 page
  • Fetches data only
  • Provides a shared shell that wraps the page and its children and stays mounted across navigation
  • Defines CSS variables

Answer: Provides a shared shell that wraps the page and its children and stays mounted across navigation. A layout wraps everything below it and persists across sibling navigations; only the inner page swaps.

In the App Router, components are by default...

  • Client components
  • Server components that run on the server and ship zero JS
  • Class components
  • Static HTML

Answer: Server components that run on the server and ship zero JS. Files under app/ are Server Components by default, rendering on the server and shipping no JS for that component.

How do you make a component interactive (state, effects, event handlers)?

  • Add 'use server' at the top
  • Use a class component
  • Wrap it in Suspense
  • Add the 'use client' directive at the top of the file

Answer: Add the 'use client' directive at the top of the file. The 'use client' directive at the top of a file opts that component into being a client component with interactivity.

Which special file provides an instant fallback while a segment's data loads?

  • error.tsx
  • loading.tsx
  • not-found.tsx
  • page.tsx

Answer: loading.tsx. loading.tsx supplies an instant Suspense fallback shown while the segment is loading.

What URL does app/products/[id]/page.tsx serve?

  • /products only
  • /[id]
  • /products/:id — a dynamic route with id in params
  • /products/id

Answer: /products/:id — a dynamic route with id in params. The [id] folder makes a dynamic route /products/:id; the matched id arrives in params.

Continue this course

Related lessons