Interview Bank · 2026
The App Router, Server vs Client Components, Server Actions, the caching layers, and the 2026 shift away from aggressive default caching. Say each answer aloud before you reveal it.
Click a card. Answer first, in one breath, then reveal.
app/ dir) = Server Components by default, nested layouts, streaming. Pages Router (pages/) = the older client-first model with getServerSideProps. App Router is the default in 2026."use client" do?"use server" do?"use server") you can call directly from a form or event — handles mutations without you writing a separate API route.app/api/.../route.ts via exported GET/POST functions — Next.js's built-in backend.layout.tsx?[id]?[id] matches any value; the segment is passed in as params so one route handles /docs/1, /docs/2, etc.<Link> over <a>?<Link> does client-side navigation (no full reload) and prefetches routes in view — fast, SPA-like transitions.In the App Router everything is a Server Component by default: it renders on the server, ships no JS to the browser, and can read the database, files, or secrets directly. Reach for a Client Component ("use client") only when you need interactivity — useState/useEffect, event handlers, browser APIs. Rule of thumb: keep components on the server, push "use client" as far down the tree (the "leaves") as you can.
Directly — the component can be an async function and you just await the data (a fetch, an ORM query, etc.) inline before returning JSX. No useEffect, no loading spinner plumbing, no separate API call from the client. The data is fetched on the server and only the rendered HTML goes to the browser, so credentials never leave the server.
Use a Server Action ("use server"): an async function called from a form's action or an event handler that runs on the server. After writing, call revalidatePath("/docs") or revalidateTag("docs") to purge the cache for that data so the next render shows fresh content. This replaces hand-rolled POST endpoints + client refetch for most mutations.
By default env vars are server-only — readable in Server Components, Route Handlers, and Server Actions, never shipped to the browser. Only vars prefixed NEXT_PUBLIC_ are inlined into the client bundle and visible to anyone. So API keys and DB URLs stay un-prefixed; only truly public values (e.g. a public site URL) get NEXT_PUBLIC_.
app/api/.../route.ts exporting GET/POST/etc. gives you real HTTP endpoints — a backend-for-frontend. With Server Actions covering most form mutations, you reach for Route Handlers when you need a public API: webhooks, third-party callbacks, serving JSON to a mobile/external client, or anything that isn't a Next.js-internal call.
loading.tsx?Next.js can stream HTML — send the shell immediately, then flush in slower parts as their data resolves. Wrap a slow component in <Suspense fallback={...}> to show a placeholder while it loads. A loading.tsx file is sugar for wrapping a whole route segment in Suspense, giving instant loading UI on navigation.
Export a static metadata object or an async generateMetadata() function from a layout or page — Next.js renders the <title>, meta, and Open Graph tags into the server-rendered HTML. Because pages are server-rendered, crawlers get real content. There's also file conventions like sitemap.ts, robots.ts, and opengraph-image.
SSG renders the page once at build time (fast, cacheable, great for content that rarely changes). SSR renders per request (always fresh, e.g. personalised pages). ISR serves static pages but revalidates them in the background on an interval or on demand — static speed with periodic freshness. In the App Router you pick these implicitly via your fetch/caching options rather than via getServerSideProps.
Two big wins. Less JavaScript shipped: Server Components render to HTML on the server and send no component JS to the browser, shrinking the bundle and speeding up load. And data & secrets stay on the server: you query the DB or read an API key right inside the component, so credentials never reach the client and there's no client-server round-trip just to render. Interactivity is added back only where you opt into "use client".
Static (prerendered at build, cached), dynamic (rendered per request when you read cookies/headers or use uncached data), streaming (send the shell, flush slow parts via Suspense), and partial prerendering (a static shell with dynamic holes streamed in — the 2026 hybrid). You rarely pick one globally; it's decided per route segment by what the code reads.
Four, roughly outer-to-inner: Request Memoization (dedupes identical fetches within one render), the Data Cache (persists fetch results across requests/deploys until revalidated), the Full Route Cache (caches the rendered HTML/RSC payload of static routes), and the client-side Router Cache (keeps visited route segments in memory for instant back/forward). Knowing these explains "why is my data stale?" — you revalidate the right layer.
The server sends static HTML so the page is visible fast; then React hydrates it in the browser — attaching event listeners and wiring up state so the static markup becomes interactive. Only Client Components hydrate. If the server-rendered HTML doesn't match what the client renders, you get a hydration mismatch error.
A single route is split into a static shell (prerendered, served instantly from the edge) with dynamic holes wrapped in Suspense that stream in per request. One page gets both the speed of static and the freshness of dynamic, without choosing between them — the direction Next.js's rendering model is heading.
useState in a Server Component throw? trickyHooks and event handlers only exist in the browser, but Server Components never run there. Calling useState/useEffect (or passing onClick) in one is an error: "you're importing a component that needs useState… add "use client"." Fix by marking the interactive piece "use client" — ideally a small leaf, not the whole page.
NEXT_PUBLIC_? trickyAny var prefixed NEXT_PUBLIC_ is inlined into the client bundle at build time — anyone can read it in the browser. Naming your API key NEXT_PUBLIC_API_KEY ships it to every visitor. Secrets must stay un-prefixed and be read only on the server. "NEXT_PUBLIC_ = shouted to the browser."
fetch caching? trickyHistorically Next.js cached fetch by default, so data looked stale and people were baffled. You control it with { cache: 'no-store' } / { next: { revalidate: N } } / tags. In Next.js 15 the default flipped — fetches are uncached unless you opt in — so the gotcha now cuts the other way (you may need to add caching back for speed).
"use client" blow up my bundle? tricky"use client" marks a boundary: that component and everything it imports become client-side. Put it high in the tree and you accidentally drag the whole subtree into the browser bundle, losing the Server Component benefits. Keep the directive on small interactive leaves and pass server-rendered children in as props/children.
No. A Server Action compiles to a public HTTP endpoint — anyone can call it with any payload, not just your form. Treat it like an API route: validate the input (e.g. with Zod) and check auth/authorisation inside the action. "It's only called from my button" is not a security control.
When the server-rendered HTML differs from the client's first render — e.g. rendering Date.now(), Math.random(), or reading window/localStorage during render, or invalid nesting like a <div> inside a <p>. Fix by making render deterministic, or deferring browser-only values to useEffect.
The big shift was away from aggressive default caching. In Next.js 15, fetch requests, GET Route Handlers, and the client Router Cache are uncached by default — you now opt in to caching instead of opting out. It killed a whole class of "why is my data stale?" surprises; the trade-off is you sometimes add caching back deliberately for performance.
Next.js 15 made the request-scoped APIs asynchronous: you now await cookies(), await headers(), and await params/searchParams. This supports the move toward partial prerendering (the static shell can render before per-request data arrives). It also ships React 19 support and Turbopack stable for dev (much faster local builds).
after()? 2026Partial Prerendering (PPR) is maturing — a static shell with dynamic, streamed holes in one route, no either/or. And after() lets you schedule work to run after the response is sent (logging, analytics, cache writes) without delaying the user. Frame both as: Next.js squeezing more out of the static/dynamic boundary.
"use client" on the leaf.revalidatePath, the chat answer streams in via Suspense, and my OpenAI key stays server-only (never NEXT_PUBLIC_)." Concrete beats abstract.