
Next.js connection function
Next.js can usually tell a component needs per-request rendering because it calls a Request-time API like cookies() or headers() — that call itself is the signal. But what about a component that produces different output per request for reasons that have nothing to do with the request itself: Math.random(), new Date(), a synchronous database query that would otherwise happily execute at build time? connection() exists specifically to give Next.js that missing signal — an explicit way to say "wait for a real incoming request before continuing," even when nothing about the code itself would normally trigger that.
Basic Usage
import { connection } from "next/server";
export default async function Page() {
await connection(); // prerendering stops here
// the following code only runs at request time
const rand = Math.random();
return <span>{rand}</span>;
}
Without connection() here, Math.random() would happily execute during prerendering — producing one fixed random value baked into the static output at build time, and serving that same "random" value to every visitor thereafter, which is almost certainly not what you want from something called Math.random().
The Case That Actually Motivates This: Synchronous Database Drivers
This is the example the docs lead with, and it's a genuinely non-obvious failure mode worth internalizing: a query from a synchronous database driver — better-sqlite3 is the named example — completes instantly, synchronously, during rendering. If you're not otherwise using a Request-time API anywhere in that component, Next.js has no signal at all that this query shouldn't just run during prerendering and get baked into static output.
import { connection } from "next/server";
import Database from "better-sqlite3";
const db = new Database("app.db");
export async function getVisitorCount() {
await connection();
return db
.prepare("SELECT value FROM counters WHERE name = ?")
.get("visitors");
}
Calling connection() before the query explicitly excludes any component that calls getVisitorCount() — and everything downstream of it — from prerendering, forcing a genuine per-request read rather than a build-time snapshot silently frozen into your static HTML. Without this, a "visitor count" feature would very plausibly ship a permanently stale number to every single visitor, with no error or warning to indicate anything had gone wrong — the query succeeds, it just succeeds at the wrong time.
Reference
function connection(): Promise<void>;
Takes no parameters, and returns a void promise that isn't meant to be consumed for its resolved value — you await it purely for its side effect of establishing the request-time boundary, not to read anything back from it.
When You Actually Need This
The docs are precise about the scope here: connection() is only necessary when you genuinely need dynamic (request-time) rendering and you're not already using one of the common Request-time APIs that would establish that dynamism implicitly. If your component already calls cookies() or headers() somewhere, that call already forces dynamic rendering on its own — reaching for connection() in that case would be redundant, not incorrect, just unnecessary.
connection() Replaces unstable_noStore
If you've seen unstable_noStore in older code or documentation, connection() is its intended successor — the docs state directly that connection exists specifically to better align with the framework's future direction than the older, explicitly-unstable API did. New code should reach for connection(), not unstable_noStore.
Under Cache Components: Prefer io() Instead, Usually
This is the detail most likely to change how you actually use this function if you're on the current Cache Components model. The docs recommend io() over connection() for the common case of excluding content from the static shell — io() accomplishes the same exclusion, but can additionally be cached and prefetched, which connection() fundamentally cannot be, since its entire purpose is forcing a wait for a genuine incoming request.
The guidance is precise: reach for connection() specifically when rendering should wait for a real user request — not merely "this shouldn't be static," but "this genuinely cannot proceed until an actual request has arrived." If your actual need is just "keep this out of the prerendered shell, but still let it be cached or prefetched when it can be," io() is the better-fitting tool; connection() is the narrower, stricter option reserved for when even caching/prefetching wouldn't be appropriate.
Version History
| Version | Changes |
|---|---|
v15.0.0 | connection stabilized |
v15.0.0-RC | connection introduced |
Key Takeaways
| Aspect | Detail |
|---|---|
| Purpose | Explicitly force a wait for a real incoming request, even without using another Request-time API |
| Classic use case | Synchronous database drivers (e.g. better-sqlite3) that would otherwise silently execute at build time |
| Return value | A void promise — awaited for its side effect, not its resolved value |
| Redundant when | You already call cookies()/headers() elsewhere in the same component |
| Supersedes | unstable_noStore |
| Under Cache Components | Prefer io() when caching/prefetching should still be possible — reserve connection() for when it genuinely shouldn't |
connection() exists to close a specific, easy-to-miss gap: code that's dynamic in spirit (it should never be baked into a static build) without being dynamic in the way Next.js can automatically detect. If you're on Cache Components, check io() first — reach for connection() only when the stricter "must wait for a real request" guarantee is genuinely what you need.


