Quiz - The server / client boundary
A teammate opens app/dashboard/page.tsx, sees a plain component with no directive at the top, and asks “so where does this run?” In an App Router project, what’s the correct read — and what does that let this file do?
It’s a Server Component (the default with no directive), so it can be async, await db.query(...) directly in its body, and read process.env secrets — none of which reach the browser.
It runs in both places by default; the directive only matters for performance tuning, so it could use useState and query the database in the same file.
It’s a Client Component until you add "use server" to opt it into server rendering.
app/ is a Server Component unless a "use client" boundary above it flips it — the absence of a directive is the opt-in to server-only execution. That’s what unlocks an async body with direct DB access and process.env reads that never ship to the client. The flip side is the cost: no useState, no event handlers, no window. There is no "use server" directive for “make this a Server Component” — Server is already the default, and "use server" means something entirely different (Server Actions).A product page renders a grid of cards, each with an interactive <AddToCart /> button. A teammate adds "use client" to the top of page.tsx “so the buttons work.” Why does a senior push back, and what’s the fix?
That directive drags the page, the grid, and every card into the client bundle. Move "use client" down to AddToCart alone — only the smallest interactive leaf and its dependencies should ship as JavaScript.
It’s fine — "use client" on the page only affects the page component itself, so the cards stay Server Components automatically.
The real fix is to keep "use client" on the page but wrap each card in <Suspense> so the JavaScript loads lazily.
"use client" marks an entry point: the file and everything it imports transitively becomes part of the client graph, so a directive at the page top pulls the whole subtree into the bundle. The senior reflex is to push the boundary down to the smallest leaf that actually needs interactivity — here, AddToCart — leaving the page, grid, and cards as zero-JS Server Components. Suspense is about streaming async UI, not about trimming what ships to the client.You want a Client Component <Tabs> (it animates, holds active-tab state) to display server-rendered article content inside each tab. Which arrangement actually works?
Render <Tabs> from a Server Component and pass the server-rendered article as children (or another prop slot) — a Client Component can receive server content it doesn’t import.
Inside Tabs.tsx, import the <Article> Server Component and render it directly — Client Components can import Server Components as long as they don’t pass them props.
import a Server Component — that would force the server module into the client bundle, which the framework forbids. The supported pattern is composition through children: a Server Component owns the import and hands the already-rendered tree to the client shell as a prop, which renders it opaquely. “Interactive shell wrapping server-rendered content” is exactly this slot pattern.Your lib/db/index.ts reads process.env.DATABASE_URL and opens a connection. Someone could one day import it from a "use client" file and silently ship the connection string to the browser. The directive convention alone won’t catch it. What’s the senior guardrail?
Add import 'server-only'; at the top of the file — if it ever reaches the client bundle, next build fails with an error pointing at the offending import chain.
Add 'use server'; at the top of the file so every export becomes server-bound and can’t be imported by a client file.
Add 'use client'; to the database file so the bundler knows to keep it out of server code.
server-only is a marker package rigged to throw if it lands in the client bundle, turning a leaked import into a build error instead of a production secret leak — exactly the structural enforcement the directive convention can’t provide. 'use server' is a different tool entirely: it marks exports as Server Actions (RPC endpoints callable from the client), which would make the leak worse, not better. 'use client' would force the DB code into the browser — the opposite of the goal.A junior types "use clinet" at the top of a new interactive component. The build succeeds, the page loads, but useState throws at runtime in production. What happened, and what’s the habit that prevents it?
A typo’d directive is silently ignored — the file stayed a Server Component, so the hooks crash at runtime. Copy the directive from a known-good file instead of typing it by hand.
The directive must be all-lowercase with single quotes; double quotes like "use client" are what actually broke it.
"use clinet" was treated as a custom directive name; you fix it by registering it in next.config.js.
"use clinet" is just an ignored string at the top of the file — no error, the file silently stays a Server Component, and the hooks blow up only when that path runs. Either quote style is valid ('use client' or "use client"), so quoting isn’t the issue, and there’s no config-based directive registration. The senior habit is to copy-paste the directive from a working file rather than retype it, since the failure mode is silent.A Server Component renders a Client <Row> and tries to pass onDelete={() => deleteUser(user.id)}. The build errors with “Functions cannot be passed directly to Client Components.” Separately, the same page passes a user prop that’s an instance of a User class with methods. Which statements are correct? Select all that apply.
The callback fails because functions don’t cross the RSC wire; the fix is to define a "use server" Server Action and pass its reference instead.
The User class instance won’t cross either — flatten it to a plain object ({ id, name, email }) at the boundary; methods and the constructor don’t survive serialization.
Wrapping the callback in JSON.stringify() before passing it makes it serializable and crossable.
A Date field on user would fail the same way the class instance does, so dates must be passed as ISO strings.
Map, Set, Date, Promises, JSX — but not functions or class instances. A closure can’t run in the browser, so pass a Server Action reference (the one function-like value the wire carries) instead. A class instance loses its methods and constructor, so flatten to a plain object at the boundary. JSON.stringify on a function yields undefined, not a callable. And Date is explicitly supported by structured clone, so there’s no need to stringify it.A Client Component renders <span>{new Date().toLocaleString()}</span> to show the current time. It looks fine on first paint, then React throws “Hydration failed because the server-rendered HTML didn’t match the client.” What’s the root cause, and the default fix?
The timestamp differs between the server render and the browser render, breaking the strict-equality handshake. Render a stable placeholder on the server and set the real time in a useEffect after mount.
The component is missing a key prop, so React can’t match the server and client nodes; add a stable key.
Server Components don’t hydrate, so the fix is to remove "use client" and let it render on the server only.
Date.now(), toLocaleString(), Math.random()) differs between the two renders — the canonical mismatch cause. The default fix is to render a server-safe placeholder, then swap in the client value inside useEffect, which runs only after hydration. key is about list reconciliation, not the server/client handshake. And removing "use client" would strip the interactivity entirely; “current time” is intentionally a client concern.Quiz complete
Score by topic