Module 5 · Next.js · Drills
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.
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.
Drill 1 server action
Write a Server Action saveNote(formData) that reads a body field and logs it, then call it from a <form>.
// 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.
"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 }.
// app/api/health/route.ts import { NextResponse } from "next/server" export async function GET() { return NextResponse.json({ ok: true }) } // visit /api/health → {"ok":true}
Drill 4 fetch cache
Fetch /docs from FastAPI but only re-fetch at most every 30 seconds. Then write the variant that never caches.
// 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.
<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.
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.
// 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.
Click a card to flip it. Say the answer out loud before you flip — that's the rep that builds storage strength.
"use server" do?revalidatePath do?route.ts exporting GET/POST… — a real endpoint, often a BFF/proxy to FastAPI.{ cache: "no-store" }; for timed freshness use { next: { revalidate: N } }.cookies().Tick each only if you can do it without looking:
actionrevalidatePath/revalidateTag after every mutationfetch cache optionnext build