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.
Related Articles#
- Next.js 15 Caching, Explained
- Fix Stale Cache and Revalidation in Next.js
- Fix revalidatePath Not Working in Server Actions
- Next.js App Router Complete Guide
Frequently Asked Questions#
What causes "Dynamic server usage" in Next.js?#
A route Next wanted to prerender called a dynamic function — cookies(), headers(), draftMode(), searchParams, or a no-store fetch — which needs the incoming request. Next normally switches to dynamic rendering automatically; the error surfaces when that switch is blocked or the call ran outside Next's tracked context.
How do I make a Next.js route dynamic?#
Add export const dynamic = 'force-dynamic' (or revalidate = 0) to the segment. It renders per request — the App Router equivalent of getServerSideProps.
Should I always add force-dynamic?#
No. If the data isn't request-specific, remove the dynamic dependency so the route stays static and fast. force-dynamic is right only when the route must render per request.
Frequently Asked Questions
One email a month — no fluff
RLS gotchas, Next.js cache debugging, and the one Supabase setting that bit me last month.
Continue Reading
Next.js Hydration Mismatch: 8 Fixes for App Router (2026)
Hydration mismatch errors breaking your Next.js app? Learn the root causes and 8 proven fixes to eliminate these errors permanently.
Fix Next.js revalidatePath Not Working in Server Actions
Your Server Action mutates data but the page shows stale values until you hard-refresh. `revalidatePath` is one of those APIs that "succeeds" while doing nothing. Here are the six reasons it no-ops, with the exact fix for each — including the one nobody tells you about: `dynamic = 'force-static'`.
Fix port setting in Next.js
Change the default Next.js port when it collides with another process. Step‑by‑step fix with code, verification, and prevention tips.
Browse by Topic
Find stories that matter to you.
