What does `connection()` from `next/server` do when awaited in a Next.js App Router server component, and what does it solve that a fetch cache option cannot?
answer
- no data read to mark uncached
- wait for an actual request
- boundary is where you await
- component scope, not segment scope
- replaced an unstable predecessor
basics
~20 sAwaiting connection() tells Next that rendering must wait for a real user request, so everything after it is excluded from prerendering. It is the opt-out for code with no data fetch at all, such as a component reading the clock or a random value.
solid answer
~40 s`connection()` is an async function exported from `next/server`. Awaiting it inside a server component says: from this point on, do not prerender — wait for an actual request. That is the piece a `fetch` option cannot give you, because cache options only describe whether a *response* is stored; they say nothing about code that reads no remote data. A component that renders `Date.now()`, `Math.random()`, or a freshly generated id has nothing to mark `no-store`, so without `connection()` it would happily be evaluated ahead of the user and the same value baked into every visit. It also gives you a component-scoped lever, which is narrower than putting `dynamic = 'force-dynamic'` on the whole segment. In Next 15 it replaced `unstable_noStore()` from `next/cache`, which is deprecated in its favour.
code
tsx · 15 linesimport { connection } from 'next/server'
export default async function LiveClock() {
// Nothing below this line is prerendered.
await connection()
const now = new Date().toISOString()
const nonce = Math.random().toString(36).slice(2)
return (
<p>
{now} · {nonce}
</p>
)
}go deeper
Recognise the name and the one-line purpose: awaiting it means this part of the page waits for a real request instead of being rendered ahead of time.
Explain why a fetch cache option cannot cover this case — there is no response to store — and show that the await position defines the boundary between prerendered and request-time work.
Demonstrate placement judgment: push the boundary as low in the tree as possible so the rest of the route keeps its prerendered output, and be clear that this controls your render only, not any cache sitting in front of it.
Own the migration angle: an inherited codebase full of the deprecated predecessor needs a considered sweep, not a find-and-replace, and the team needs a rule for which components are allowed to become request-time.
## The gap it fills Every other caching opt-out in Next attaches to a *data read*. You mark a `fetch` uncached, or you mark a cached function's entry short-lived. But a route can be wrong in a way that has nothing to do with data: ```tsx export default async function Page() { const nonce = Math.random() // evaluated whenever this renders const now = new Date().toISOString() // same return <p>{now} · {nonce}</p> } ``` If this route is prerendered, that render happens once, ahead of any user, and the timestamp and the random number are frozen into the output. There is no fetch here to mark `no-store` — the freshness problem is in the *execution*, not in the storage of a response. `connection()` exists for exactly this case. ```tsx import { connection } from 'next/server' export default async function Page() { await connection() const now = new Date().toISOString() return <p>{now}</p> } ``` ## What awaiting it means `connection()` returns a promise that is only meaningful once there is a real incoming request. During prerendering there is no request, so the render cannot proceed past the `await` — the work after it is excluded from the prerendered output and deferred to request time. That is the whole contract, and it explains two practical rules: 1. **Await it before the request-time code, not after.** Anything above the `await` may still be evaluated ahead of time. The boundary is positional. 2. **It marks the component it is in.** Put it in the small component that genuinely needs request time rather than in the page or, worse, a shared layout — the narrower the placement, the more of the route stays prerenderable. ## Versus the segment config `export const dynamic = 'force-dynamic'` answers the same need at a coarser grain: it declares the entire segment request-time. `connection()` is the component-scoped version of the same intent. Given a page where one badge shows a live timestamp and everything else is stable, the badge with an `await connection()` in it lets the rest of the page keep being prerendered, whereas the segment flag gives that up for the whole route. ## Versus `unstable_noStore` Before `connection()`, the same job was done by `unstable_noStore()` imported from `next/cache`: ```ts import { unstable_noStore as noStore } from 'next/cache' ``` It was always marked unstable, and Next 15 deprecated it in favour of `connection()`. You will still meet it in older codebases and in blog posts, and knowing that it is the predecessor — rather than a different feature — is a genuinely useful piece of interview trivia. When you migrate, the replacement is `await connection()`; note the `await`, since the old call was synchronous. ## The cost, and the honest caveat Everything below the `await` stops being prerenderable, so you pay a server render for that work on every request. Keeping the boundary low in the tree is what keeps that bill small. The caveat worth stating out loud in an interview: `connection()` is about your *own* render. It does not instruct a CDN or a browser about what to store. If a shared cache in front of the app is holding the response, per-request rendering on your side does not by itself make what the user sees fresh — that is a separate layer with its own controls. ## When you probably do not need it If the component already reads request-scoped input — the incoming cookies, headers, or search params — it is already tied to a real request, and adding `connection()` on top is noise. Reach for it specifically when nothing in the component *looks* request-dependent to Next but the output still must differ per request: clocks, randomness, generated identifiers, or a deliberate "never serve this from a prerender" marker in code that would otherwise seem perfectly static.
- Does it matter where in the component body the await sits?Yes — the boundary is positional. Code above the `await connection()` can still be evaluated during prerendering; only what follows it is deferred to request time. So put the await before the clock read, the random value, or whatever must not be frozen, and keep as little as possible below it.
- How is this different from just putting force-dynamic on the page?Scope. `force-dynamic` declares the whole segment request-time, so nothing in that route can be prerendered. `connection()` marks one component, letting the rest of the route keep its prerendered output. Same intent, much smaller blast radius — prefer it when only a small region is genuinely request-dependent.
- You inherit a codebase calling `unstable_noStore()` from `next/cache`. What do you do?Treat it as the predecessor and migrate to `await connection()` from `next/server`, which Next 15 introduced to replace it. Mind the difference in shape: the old call was synchronous, the new one must be awaited, so a mechanical find-and-replace that drops the `await` leaves the boundary doing nothing.
saying these in an interview costs you the question
- Thinks it disables the browser or CDN cache
- Calls it without awaiting and expects a boundary
- Places it at the bottom of the component body
- Believes it is an alias for force-dynamic at segment scope
- Uses it in components that already read cookies or headers