Quiz - The request surface
A teammate ships an auth gate in proxy.ts. It reads the session cookie, verifies it against the database, and bounces signed-out users — and they’ve decided that since the proxy now does the real check, the routes themselves no longer need one. What’s wrong with this plan? Select all that apply.
proxy.ts is for, and duplicating it in every route is wasteful.requireUser(), fresh every time.A Server Component reads (await headers()).get('x-forwarded-for') to get the client IP, and another component reads a custom x-user-role header to decide whether to show an admin panel. Which read is the dangerous one, and why?
x-user-role read — it makes an authorization decision from a header, which any client can forge. Identity and permission come from the verified session, never a raw header.x-forwarded-for read — IP addresses are personal data, so reading one without consent is the real risk; the role header is fine because the app set it.x-user-role: admin. Authorizing off one is a textbook privilege-escalation bug. x-forwarded-for is only trustworthy because a known proxy (Vercel) overwrites it, and even then it’s for telemetry, not identity. The anchor: headers are for telemetry and platform signals; the session decides who someone is and what they may do.Your SaaS permanently moves /account to /settings, and separately bounces already-logged-in users away from /login. A reviewer says “just use NextResponse.redirect(url) for both and you’re done.” What does that miss?
/account move should be a 308 (permanent) but redirect() defaults to 307; and the /login bounce should stay 307 because it depends on the user’s session — a 308 there gets cached as “never visit /login,” wrong once they log out.next.config.ts instead, because any redirect is cheaper at the CDN edge regardless of whether it reads the request.NextResponse.redirect(url) already sends a permanent redirect by default, which is correct for both cases.redirect() defaults to 307 (temporary). The /account rename is a genuine permanent move, so it earns an explicit 308 that lets search engines reindex. The /login bounce is a fact about this user right now, not the URL, so it stays 307 — a wrong 308 gets cached and indexed and is painful to undo. Under-commit to 307 unless a move is truly forever.Building an invoice list, you wire the status filter into useState, refetch in a useEffect, and render. It works on your screen. Why does a senior reach for URL state instead?
useState can’t hold a filter value reliably across re-renders, so the URL is the only place the value stays stable.useState+useEffect version rebuilds the exact fetch-in-an-effect waterfall the effects chapter warned against, and it dies on refresh and can’t be shared.A 'use client' filter chip needs to show whether it’s the active filter and, on click, change the URL to its status while keeping the existing ?sort and ?cursor params. Which approach is right?
new URLSearchParams from the current query, set the one key, and router.replace(..., { scroll: false }).useSearchParams for live accuracy, and on click router.push('?status=paid') so the back button can step through each filter.useSearchParams, and on click router.replace('?status=paid') — the shortest correct write.useSearchParams (which would also drag in a Suspense boundary). For the write, router.replace('?status=paid') overwrites the entire query and silently drops sort and cursor; you merge by seeding URLSearchParams from the current query and setting one key. replace (not push) keeps filter churn out of history, and scroll: false stops the viewport from jumping.Quiz complete
Score by topic