Module 5 · Next.js · Drills

Drills: Next.js Advanced

Reading is not knowing. Type every one of these yourself — in a real Next.js App Router project — before you reveal the solution. Effortful recall is the point.

How to use this page Each drill is a small task in an app/ directory. Attempt it first, run next dev, 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 server action

Write a Server Action saveNote(formData) that reads a body field and logs it, then call it from a <form>.

Show solution
// app/notes/actions.ts
"use server"
export async function saveNote(formData: FormData) {
  console.log(formData.get("body"))
}

// app/notes/page.tsx
import { saveNote } from "./actions"
export default function Page() {
  return (
    <form action={saveNote}>
      <input name="body" /><button>Save</button>
    </form>
  )
}

Drill 2 revalidatePath

Add revalidatePath to a Server Action so the /docs page refreshes after a write.

Show solution
"use server"
import { revalidatePath } from "next/cache"

export async function addDoc(formData: FormData) {
  await saveToBackend(formData)   // your write
  revalidatePath("/docs")            // list re-renders
}

Rule of thumb: write, then revalidate. Skip it and the UI shows stale data.

Drill 3 route handler

Create a Route Handler at app/api/health/route.ts that responds to GET with { ok: true }.

Show solution
// app/api/health/route.ts
import { NextResponse } from "next/server"

export async function GET() {
  return NextResponse.json({ ok: true })
}
// visit /api/health → {"ok":true}

B · Stretch Intermediate

Drill 4 fetch cache

Fetch /docs from FastAPI but only re-fetch at most every 30 seconds. Then write the variant that never caches.

Show solution
// revalidate every 30s (incremental)
const res = await fetch(url, { next: { revalidate: 30 } })

// always fresh — opt out of caching entirely
const live = await fetch(url, { cache: "no-store" })

In Next 15 fetch is uncached by default — revalidate opts you into caching with a freshness window.

Drill 5 tailwind

Style a button: rounded corners, red-700 background, white text, padded, darker on hover.

Show solution
<button className="rounded-md bg-red-700 px-4 py-2 text-white hover:bg-red-800">
  Upload
</button>

Each utility is one CSS rule: rounded-md radius, px-4 py-2 padding, hover: the hover state.

C · Build challenge Build

Mini-project Write a Route Handler that proxies a POST to FastAPI with the auth cookie attached. The browser calls your Next.js endpoint; the FastAPI token never touches client code. This is the exact BFF layer DocChat ships with.

Build · proxy POST with auth cookie

At app/api/ask/route.ts, read the request body, read the token cookie, and forward the POST to FastAPI's /ask endpoint.

Show solution
// app/api/ask/route.ts
import { NextRequest, NextResponse } from "next/server"
import { cookies } from "next/headers"

export async function POST(req: NextRequest) {
  const body = await req.json()
  const token = (await cookies()).get("token")?.value

  const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/ask`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${token}`,
    },
    body: JSON.stringify(body),
    cache: "no-store",
  })

  const data = await res.json()
  return NextResponse.json(data, { status: res.status })
}

The token is read server-side from the httpOnly cookie and forwarded — it's never serialized into anything the browser can read. Returning res.status keeps FastAPI's error codes intact.

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 "use server" do?
Marks a function (or file) as server-only — callable from the client, runs on the server.
click to flip
What is a Server Action?
An async function invoked from a form/handler that mutates data server-side, no API route needed.
click to flip
What does revalidatePath do?
Marks a route's cached data stale so it re-renders with fresh data after a mutation.
click to flip
What is a Route Handler?
A route.ts exporting GET/POST… — a real endpoint, often a BFF/proxy to FastAPI.
click to flip
Fetch option for always-fresh?
{ cache: "no-store" }; for timed freshness use { next: { revalidate: N } }.
click to flip
Why an httpOnly cookie for the JWT?
Client JS can't read it, so XSS can't steal it; read it server-side via cookies().
click to flip

E · Self-check before moving on

Tick each only if you can do it without looking:

Next All ticked? Your frontend is now a real full-stack layer. Next we build the brain behind DocChat: Module 6 — RAG Foundations.