Await in the leaf

Pass request promises down; await them inside a Suspense child.

Next.js docsMessage · blocking-prerender-runtime
Awaiting request-time data at the top of a page/layout makes everything below it dynamic.
Error messageRead docs ↗

From next-cache-components-optimizer, patterns.md and real-app-patterns.md.

One await, whole page waits

Pick what the route reads, then press Request.

Request data the route reads
Await at the topBad
Await in the leafGood

Defer the auth gate

Pick a visitor, then press Visit.

Visitor
Gate at the topBad
await getSession() then redirect()
Gate in SuspenseGood
<Suspense fallback={null}><AuthGate />
// app/settings/layout.tsx
import { Suspense } from 'react'

export default function SettingsLayout({ children }) {
  return (
    <Shell>
      <Suspense fallback={null}>
        <AuthGate />
      </Suspense>
      {children}
    </Shell>
  )
}

async function AuthGate() {
  const session = await getSession()
  if (!session?.user) redirect(getLoginUrl())
  return null
}

Why it matters, pros and cons

Why it matters

A top-level await suspends the prerender, so nothing below it reaches the static shell.

Pros

  • Nav, heading and layout paint from the shell.
  • Only the part that reads request data waits.
  • redirect() still runs, at request time.

Cons

  • Each reader needs its own boundary and fallback.
  • A deferred gate lets the frame paint; keep user data out of it.
  • A page that is all URL data has little shell to gain.