In a Next.js App Router page, a layout and two Server Components each call fetch('https://api.example.com/user') while rendering the same request. How many requests actually leave the server, and what does that deduplication not do for you?
answer
- one render, not one session
- same URL and same options
- React's extension, not Next's
- nothing survives the render
- only covers fetch
basics
~20 sOne. React memoizes fetch calls with the same URL and options within a single server render, so the three calls collapse into one. That memoization is scoped to that one render: it is not shared across requests, users, or non-fetch data access.
solid answer
~50 sOne request leaves the server. React extends `fetch` so that calls with the same URL and the same options during one render pass are deduplicated — that is Request Memoization, and it exists precisely so you can call a `getUser()` helper wherever you need the data instead of threading a result down through props. What it does not do is act as a cache. Its scope is one render of one request: the next request re-fetches, another user gets their own call, and the memo table is dropped when the component tree finishes. It keys on the request options too, so two calls with different headers are two entries. It also only covers `fetch` during the React render — a direct database or SDK call has no such deduplication, and work outside the component tree is not part of that render pass.
code
typescript · 29 lines// app/lib/user.ts
export async function getUser() {
const res = await fetch('https://api.example.com/user')
if (!res.ok) throw new Error('failed to load user')
return res.json()
}
// app/dashboard/page.tsx
import { getUser } from '../lib/user'
async function Greeting() {
const user = await getUser()
return <h1>Hello {user.name}</h1>
}
async function Avatar() {
const user = await getUser()
return <img src={user.avatarUrl} alt="" />
}
export default function Page() {
// Both components call getUser(); one request leaves the server per render.
return (
<>
<Greeting />
<Avatar />
</>
)
}go deeper
Know that calling the same fetch helper from several Server Components in one page does not mean several network requests, so you do not have to pass the result down as props just to avoid duplicates.
Explain the mechanism and its key: same URL plus same options, deduplicated for one render pass. Be explicit that this is deduplication, not storage, and that it comes from React rather than Next.js.
Demonstrate the boundary in production terms — recognising from upstream request counts whether you are looking at memoization, a stored copy, or a helper whose varying options defeat both.
Set the convention: where data-access helpers live, what makes their inputs stable enough to deduplicate, and when a team should reach for explicit memoization around non-fetch sources rather than fanning out queries.
## What actually happens Three calls, one network request. React wraps `fetch` with a memoization table scoped to a single render of the component tree. The first call to a given URL-and-options combination issues the request and stores the in-flight promise; the second and third calls find the entry and receive the same result. When the render finishes, the table is thrown away. ```ts // lib/user.ts export async function getUser() { const res = await fetch('https://api.example.com/user') return res.json() } ``` Call `getUser()` from a layout, from the page, and from a component three levels down, and the upstream API sees one request for that render. ## Why the framework wants this Before Server Components, the standard way to avoid duplicate requests was to fetch once high in the tree and pass the result down, or to install a client-side data library whose whole job was request deduplication. Both couple components that have no business knowing about each other: a leaf that needs the current user has to accept it as a prop from a parent that only holds it in transit. Memoization removes that pressure. Each component asks for what it needs, at the point where it needs it, and the framework guarantees the underlying request happens once. The technical name for this in the App Router docs is Request Memoization, and it is worth saying out loud in an interview that it comes from React rather than from Next.js — that framing is what makes the rest of its behaviour predictable. ## The four things it does not do **It is not a cache across requests.** This is the distinction that separates a candidate who has read the docs from one who has skimmed them. Memoization answers "did I already ask for this during this render?" A cache answers "do I have a stored copy from earlier?" The second request from the same user, a second later, re-fetches. Another user rendering the same page re-fetches. There is no lifetime to configure and no way to invalidate it, because nothing outlives the render that created it. It is also independent of whether the response is cached at all. A request you have explicitly opted out of caching is still deduplicated inside one render, because these are separate mechanisms operating at different scopes. **It keys on options, not just the URL.** Two calls to the same URL with different headers, methods, or bodies are separate memo entries and separate requests. A frequent real-world surprise is a helper that attaches a per-call header — a request ID, a varying `Accept` — and quietly defeats deduplication for calls that look identical in the source. **It applies to `fetch` during the render.** If your data access does not go through `fetch` — a database driver, a cloud SDK, a file read — nothing deduplicates it, and three components calling the same helper produce three round trips to your database. That gap is the reason a separate per-render memoization helper exists for non-`fetch` work. **It is scoped to the React render tree.** The memo table belongs to a render pass. Code that runs outside that pass, such as a route handler, is not participating in it. ## How to tell it apart in an interview A clean way to demonstrate you understand the boundary is to describe the two symptoms: - If your upstream API sees *one* request per page render even though three components asked, that is memoization working. - If your upstream API sees *zero* requests across many page renders over minutes, that is a stored copy being served, which is a different layer entirely. ## Practical consequences Two habits follow. First, stop threading fetched data through props for deduplication reasons alone in Server Components — call the helper where you need it and let the framework collapse the calls. Pass data down when the shape of the component tree calls for it, not to save a request. Second, when you build a data-access helper, keep its inputs stable. A helper whose options vary per call site (a fresh correlation header, a timestamp in a query string) silently produces one upstream request per caller. If you are debugging "why does my API see N identical requests per render", diffing the exact URL and options each call site produces is the first place to look.
- Does that deduplication help if the two components pass different headers to the same URL?No. The memo key includes the request options, so differing headers, methods, or bodies produce separate entries and separate network requests. A helper that attaches something per call — a fresh correlation ID, a varying `Accept` — will quietly issue one upstream request per call site even though the URL is identical.
- What happens if the three components call a database driver directly instead of fetch?Nothing deduplicates them; you get three round trips. Request Memoization only wraps `fetch`. For non-`fetch` data access you need an explicit per-render memoization helper around the function, otherwise every component that asks for the same row pays for it separately.
saying these in an interview costs you the question
- Calls it a cache and gives it a TTL
- Thinks the next request reuses the memoized result
- Assumes it covers direct database calls too
- Believes it is shared between users on the server