Fix Dynamic Server Usage Error in Next.js App Router
Next.js throws this when a route it wants to render at build time calls a dynamic function — cookies(), headers(), searchParams, or a no-store fetch. The fix is either opt the route into dynamic rendering, or remove the request-time dependency if it should be static.
Your build fails, or a route logs at runtime:
Dynamic server usage: Route /dashboard couldn't be rendered statically
because it used `cookies`.(Swap cookies for headers, searchParams, or no-store fetch.) This is DYNAMIC_SERVER_USAGE. It is not a bug — it's Next.js telling you a route it wanted to prerender at build time depends on request-time data. The fix is a decision: should this route be dynamic, or should it not need that data at all?
The mental model: build time vs request time#
- Static rendering = the route is rendered at build time, cached, CDN-shareable.
- Dynamic rendering = the route is rendered per request.
The following are dynamic functions — they depend on the incoming request and force dynamic rendering:
cookies()andheaders()(fromnext/headers)searchParams(the page prop)draftMode()- a
fetch(..., { cache: 'no-store' })(orrevalidate: 0)
Per the docs: "During rendering, if a dynamic function or uncached data request is discovered, Next.js will switch to dynamically rendering the whole route." Normally that switch is automatic and silent — Next throws DynamicServerError internally and catches it. You see the error "when it's uncaught" — typically because the route was forced static (dynamic = 'error', a static route handler, generateStaticParams, sitemap/metadata files) while a child still calls a dynamic API, or the call ran outside Next's tracked async context.
Fix 1 — Opt into dynamic rendering (when the data IS request-specific)#
// app/dashboard/page.tsx
export const dynamic = 'force-dynamic'The docs describe this as forcing "routes being rendered for each user at request time... equivalent to getServerSideProps() in the pages directory." This is the right fix when the route genuinely needs per-request data (the user's session, a header, query params).
export const revalidate = 0 achieves the same dynamic outcome via the caching model. Both are valid; force-dynamic is the more explicit switch.
Fix 2 — Remove the dynamic dependency (when it SHOULD be static)#
If the data isn't actually request-specific, don't go dynamic — fix the cause so the route stays fast and cacheable:
- Reading a cookie you don't need? Remove the
cookies()call. - Fetching with
cache: 'no-store'for data that rarely changes? Switch tocache: 'force-cache'orrevalidate: N. - Want it fully static?
export const dynamic = 'force-static'forcescookies(),headers(), anduseSearchParams()to return empty values.
Fix 3 — Isolate the dynamic part in Suspense#
Keep most of the page static and stream only the request-time piece:
import { Suspense } from 'react'
export default function Page() {
return (
<>
<StaticHeader />
<Suspense fallback={<Skeleton />}>
<UserPanel /> {/* this one reads cookies() */}
</Suspense>
</>
)
}Without Partial Prerendering, a dynamic API still makes the whole route dynamic — Suspense buys you streaming, not a static shell. With PPR enabled it becomes the canonical way to keep a static shell plus a dynamic hole. Don't oversell Suspense as a static-shell fix on stable Next 14/15 without PPR.
Route segment config reference#
export const dynamic = 'auto' // 'auto' | 'force-dynamic' | 'error' | 'force-static'
export const revalidate = false // false | 0 | number (Node.js runtime only)
export const dynamicParams = true // true | falseValues must be statically analyzable — revalidate = 600 is valid, revalidate = 60 * 10 is not.
In Next.js 15, cookies, headers, draftMode, params, and searchParams became asynchronous — you must await them: const cookieStore = await cookies(). In 13.4/14 they were synchronous. There's a codemod: npx @next/codemod@canary next-async-request-api .. Also note Next 15 stopped caching GET route handlers and client navigations by default, which shifts where this error shows up.
- You call
cookies()/headers()insidesetTimeout/setIntervalor after an un-awaited promise — that's a separate async-context bug; read the value first, then enter the deferred context. - You're on Next.js 16 with Cache Components enabled — the
dynamic/revalidateroute configs are removed there; this guide targets stable Next 14/15 semantics.
Official references: dynamic-server-error, app-static-to-dynamic-error, route segment config, Next.js 15 blog.
Declaring dynamic intent explicitly with connection() and unstable_noStore()#
The three fixes above all route through the same mechanism — export const dynamic
segment config, or removing/keeping a dynamic function call. But sometimes neither fits:
you want the route to be dynamic for a reason that has nothing to do with cookies,
headers, or fetch caching — for example, a component that reads Math.random() or the
current timestamp and must never be statically frozen at build time. Reaching for a fake
cookies() call just to trigger dynamic rendering is a common workaround, and it's
exactly the kind of stray dependency Fix 2 above tells you to remove.
Next.js has two purpose-built APIs for this instead of a segment-wide export:
import { unstable_noStore as noStore } from 'next/cache'
export default async function Component() {
noStore() // opts this data access out of caching, without a fake dynamic function
const data = await getData()
return <div>{data}</div>
}unstable_noStore() marks the surrounding scope as needing fresh data on every render —
functionally equivalent to cache: 'no-store' on a fetch, but usable around any
async call, not just fetch. The newer connection() API (from next/server) is
the more explicit successor: awaiting it tells Next.js "this component needs the
incoming request to exist before it can render," which forces dynamic rendering the
same way cookies() does, without touching any actual request data. Both are meant for
exactly the case this error covers — a route that legitimately can't be prerendered —
and are the cleaner alternative to the workaround this page's Fix 1 describes. For the
caching model these interact with, see Next.js 15 Caching, Explained.
generateStaticParams and this error on the same route#
A route with a dynamic segment (app/blog/[slug]/page.tsx) that exports
generateStaticParams tells Next.js which param values to prerender at build time —
but that's independent of whether the rendered page also calls a dynamic function.
It's entirely possible to prerender every known slug and still hit this error, if the
page component also reads searchParams or calls cookies() for something like a
draft-preview check.
What actually happens: Next.js prerenders each param from generateStaticParams
first. If, during that prerender, the component tree hits an uncaught dynamic API call,
you get the same dynamic-server-usage error described above — the static param list
didn't prevent it, because the two mechanisms answer different questions
(generateStaticParams: which slugs exist; dynamic functions: can this render
without a live request). The dynamicParams config only controls what happens for
slugs not in the returned list (true renders them on demand, false returns a
404) — it has no effect on whether a listed slug's own render is static or dynamic.
The practical fix is the same decision tree as the rest of this page: if the per-request check (draft mode, a preview cookie) is genuinely needed on every visit, accept the dynamic render for that segment; if it's only relevant to a small admin-only path, split it into its own route so the public, statically-generated pages stay fast. For the broader tradeoffs of prerendering strategy in the App Router, see Next.js Pages Router to App Router migration guide.
Further reading: Fix ChunkLoadError: Loading Chunk Failed in Next.js. Also see Fix Next.js: Unable to Acquire the Dev Server Lock.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
Continue Reading
Fix Next.js: Unable to Acquire the Dev Server Lock
Find the server that owns .next/dev/lock, distinguish a held lock from a leftover file, and restart without disrupting other Node applications.
Fix ChunkLoadError: Loading chunk failed (Next.js, webpack)
"ChunkLoadError: Loading chunk 5760 failed" almost always means a user has an old tab open after you shipped a new deploy. The fix is configuration — deploymentId, build IDs, and CDN headers — not a try/catch.
Fix UnhandledSchemeError: Reading from "node:buffer"
Reproduced on Next.js 16.4.0 with webpack: which import triggers the error, why bare "buffer" builds but "node:buffer" does not, and the three fixes that compile.


