Module 5 · Next.js · Drills

Drills: App Router

Reading is not knowing. Build every one of these yourself — in a real create-next-app project — before you reveal the solution. Effortful recall is the point.

How to use this page Each drill is a small task in your Next.js app. Attempt it first, run npm run dev and check the route in the browser, then click "Show solution" to compare. If yours works differently but correctly — great, that's fluency. Tick each box as you go; your progress is saved in this browser.

A · Warm-up reps Basic

Drill 1 routing

Create a new route at /about that renders an <h1> saying "About DocChat". Which file and folder do you need?

Show solution
app/about/page.tsx
export default function AboutPage() {
  return <h1>About DocChat</h1>;
}

The folder name is the URL; page.tsx makes it a real route. Visit /about.

Drill 2 layout

Add a layout.tsx for the /about section that wraps the page in a <main> with a shared heading above the page content.

Show solution
app/about/layout.tsx
export default function AboutLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <main>
      <p>DocChat</p>
      {children}
    </main>
  );
}

A layout must render {children} — that's where the page slots in. It persists across navigations within the section.

Drill 3 dynamic route

Create a dynamic route /users/[id] that prints "User <id>" using the value from the URL.

Show solution
app/users/[id]/page.tsx
export default function UserPage({
  params,
}: {
  params: { id: string };
}) {
  return <p>User {params.id}</p>;
}

The folder [id] matches any value; it arrives on params.id. Visit /users/42.

B · Stretch Intermediate

Drill 4 server fetch

Write a server component at /health that awaits your FastAPI /health endpoint and renders the returned status. No useEffect.

Show solution
app/health/page.tsx
export default async function HealthPage() {
  const res = await fetch(`${process.env.API_URL}/health`, {
    cache: "no-store",
  });
  const data = await res.json();
  return <p>Backend: {data.status}</p>;
}

No "use client", so it runs on the server and can be async. The fetch resolves before the HTML ships.

Drill 5 use client

Convert a static greeting into a client component with a button that toggles between "Hi" and "Bye" using useState.

Show solution
app/components/Greeting.tsx
"use client";
import { useState } from "react";

export default function Greeting() {
  const [hi, setHi] = useState(true);
  return (
    <button onClick={() => setHi(!hi)}>
      {hi ? "Hi" : "Bye"}
    </button>
  );
}

The first line "use client" is what unlocks useState and onClick. Without it, both would error.

C · Build challenge Build

Mini-project Build the DocChat document detail page at /documents/[id]: a server component that fetches one document from FastAPI and renders its filename and content. This is the screen a user lands on after clicking a document in the list you built in the lesson.

Build · document detail page

Backend GET /documents/<id> returns { "id": 1, "filename": "intro.pdf", "content": "..." }. Fetch it on the server using params.id and render it; throw on a missing document.

Show solution
app/documents/[id]/page.tsx
type Doc = { id: number; filename: string; content: string };

export default async function DocumentPage({
  params,
}: {
  params: { id: string };
}) {
  const res = await fetch(
    `${process.env.API_URL}/documents/${params.id}`,
    { cache: "no-store" }
  );
  if (!res.ok) throw new Error("Document not found");
  const doc: Doc = await res.json();

  return (
    <article>
      <h1 className="filename">{doc.filename}</h1>
      <p>{doc.content}</p>
    </article>
  );
}

Note the shape: read params.id → interpolate into the server-side fetch → throw on failure so error.tsx catches it → render. Add a loading.tsx in the same folder for a free spinner. This is the same pattern as the list page, scoped to one record.

D · Rapid recall Flashcards

Click a card to flip it. Say the answer out loud before you flip — that's the rep that builds storage strength.

What does page.tsx do?
Makes its folder a route — the folder path becomes the URL.
click to flip
What is layout.tsx for?
Wraps every page beneath it; must render {children}. Shared shell.
click to flip
Server vs client component?
Server is the default — async, fetches data, no state. Client opts in for interactivity.
click to flip
What does "use client" do?
First line of a file; makes it run in the browser so it can use state, effects, onClick.
click to flip
How do you make a dynamic route?
Name a folder [id]; read the value from params.id.
click to flip
When use NEXT_PUBLIC_?
Only for values safe to expose — it's inlined into the browser bundle. Never secrets.
click to flip

E · Self-check before moving on

Tick each only if you can do it without looking:

Next All ticked? You can build and explain the App Router's core. Next we go full-stack: forms, mutations, route handlers, and the advanced patterns that wire DocChat's UI to its backend end-to-end: Lesson 5.2 — Next.js Full-Stack & Advanced.