Moving rendering from a Client Component into a Server Component in the Next.js App Router removes JavaScript from the bundle, but the work is not free. What does the browser download instead, and how does that cost behave differently from a JavaScript chunk?
answer
- you still download something
- a description of the rendered output
- sized by data, not by code
- per navigation versus once, cached
- big lists can lose the trade
basics
~20 sThe browser downloads the RSC payload: a serialized description of the rendered server tree. It scales with the data rendered and is re-fetched per navigation, whereas a hashed JavaScript chunk is downloaded once, cached long-term, and reused across every route that needs it.
solid answer
~50 sServer rendering swaps one cost for another. A client component ships a JavaScript chunk: a fixed, code-sized cost that is downloaded once, cached under an immutable hashed URL, and reused on every route and every visit — but it must also be parsed and executed on the main thread each time it runs. A Server Component instead produces an **RSC payload**: a streamed, serialized description of the rendered tree, including the props of any client components inside it. That is data-sized, not code-sized, and it is fetched again for each navigation, so a component rendering a 5,000-row table server-side can move more bytes per visit than the sorting code would have. In current App Router versions (Next 15 and 16) Next keeps a client-side Router Cache to avoid re-fetching payloads for routes you revisit, but its staleness rules have shifted between majors, so reason about the mechanism rather than about a default.
code
typescript · 16 lines// Loses the trade: the rendered rows are re-serialized on every navigation
export default async function Page() {
const rows = await db.order.findMany({ take: 5000 })
return (
<table>
<tbody>
{rows.map((r) => (
<tr key={r.id}>
<td>{r.customer}</td>
<td>{r.total}</td>
</tr>
))}
</tbody>
</table>
)
}go deeper
Know that server rendering does not make the transfer disappear — the browser still receives a payload describing what was rendered. Be able to say that JavaScript chunks are cached and reused while that payload is fetched per navigation.
Explain the two cost shapes precisely: code-sized and cacheable versus data-sized and recurring, plus the parse-and-execute cost that only the JavaScript side pays. Name a case where each side wins.
Demonstrate that you decide with numbers — First Load JS per route on one side, payload transfer per navigation on the other — and that you can spot the regression where a data-heavy render was relocated rather than a dependency removed.
Own the policy: which parts of the product are allowed to render data-heavy trees on the server, what payload budget a route carries, and how the team is kept from optimising one metric into a regression on the other.
## Two different currencies The naive framing of Server Components is "less JavaScript, therefore faster." The honest framing is that you are trading a **code-sized, cacheable, one-time** cost for a **data-sized, recurring** one. A strong answer names both and says when each wins. ## What a client chunk costs When a component is a Client Component, its module and everything it imports are compiled into browser chunks. That cost has a specific shape: - **Sized by code**, not by data. A table component costs the same whether it renders 10 rows or 10,000. - **Downloaded once.** Chunks are served under content-hashed filenames with long-lived immutable caching, so a repeat visitor pays nothing on the wire. - **Reused across routes.** If three routes use the same component, they share the chunk. - **But re-parsed and re-executed.** Every load pays JavaScript parse, compile and execute time on the main thread — the expensive part on low-end devices — and the component then renders in the browser. ## What the RSC payload costs When the component is a Server Component, the browser instead receives the RSC payload for that render. Conceptually it is a stream of instructions describing the rendered output: element trees, text, and references to client components together with the props they should be mounted with. - **Sized by data.** Render 10,000 rows on the server and 10,000 rows' worth of markup description crosses the wire. - **Fetched per navigation.** On a fresh document load the payload is inlined alongside the HTML; on a client-side navigation Next fetches the payload for the target route rather than a new HTML document. - **Not shared across routes.** Two routes rendering similar trees each produce their own payload. - **But nearly free to apply.** There is no user code to parse or execute for the server-rendered parts; React reconciles the described tree. Next does keep a client-side Router Cache so that going back to a route you just visited can reuse its payload, and prefetching warms it. The important caveat for interviews: **the staleness defaults of that cache have changed across Next majors** (this answer assumes Next 15/16 App Router), so answer in terms of "there is a client router cache, keyed per route segment, and here is how I would verify what it is doing" rather than quoting a number. ## When each side wins Server rendering wins when the output is much smaller than the code that produced it: markdown parsing, date and currency formatting, i18n, syntax highlighting, chart data preparation, anything driven by a large dependency that emits modest markup. It also wins when the component is rendered once and never re-renders in response to user input. Client rendering wins when the same code is reused across many renders of large or rapidly changing data. A grid whose sorting, filtering and virtualisation happen locally ships its sorter once and then re-renders thousands of rows with zero network cost; pushing that to the server means a round trip and a fresh payload per column click. ```tsx // Server-side: 60 kB of formatting code stays home, ~2 kB of text ships <PriceList items={items.map(i => ({ ...i, label: fmt.format(i.price) }))} /> // Client-side: 6 kB sorter ships once, sorting 5,000 rows costs no network 'use client' export function SortableTable({ rows }) { /* sorts locally */ } ``` ## The measurement that settles it Do not argue this from first principles in a real codebase — measure both sides. `next build` reports First Load JS per route, which is the client half. The payload half you observe in the network panel: on a client navigation, look at the size of the request Next makes for the target route. A change that removes 40 kB of JavaScript and adds 200 kB of payload per navigation is a regression, and only the numbers tell you. ## The failure mode to name The classic mistake is moving a data-heavy render to the server and treating the resulting payload as free because "there is less JavaScript now." A list of 5,000 fully rendered rows serialized on every navigation is worse than the small client component that rendered them from a compact JSON array. Pushing rendering to the server is a win when it shrinks *code*, not when it merely relocates the *data*.
- Give a concrete case where moving a component to the server makes the page slower.A virtualised 5,000-row table with client-side sorting. As a Client Component it ships a few kilobytes of sorting code once, then re-sorts locally with no network cost. Moved to the server, every column click becomes a round trip that re-serializes the whole rendered table into a fresh RSC payload — far more bytes per interaction than the code it replaced.
- Does the RSC payload also carry the props of Client Components inside the tree?Yes. The payload references each client component and includes the serialized props it should be mounted with, so those values travel on every render that produces a payload. That is why passing a large object into a Client Component costs bytes on every navigation, and why passing an id and fetching by it can be cheaper.
- How would you measure both halves of this tradeoff before committing to the change?Take a baseline with `next build` for First Load JS per route, then look at the network panel for the size of the payload request during a client-side navigation to that route. Make the change and repeat both. Judge on the pair of numbers for a realistic data volume, not for an empty fixture.
saying these in an interview costs you the question
- Claims Server Components mean nothing is downloaded
- Treats the RSC payload as HTML sent again
- Thinks payload size depends on component code size
- Assumes the payload is cached like a hashed JS chunk
- Says less JavaScript is always the faster choice