Next.js 'Dynamic Server Usage' Error: Causes and Fix

Seeing 'Dynamic server usage: Route couldn't be rendered statically' in your Next.js build? Learn the 4 causes (cookies, headers, fetch, searchParams) and the exact fix for each.

The Next.js "Dynamic server usage" error means a route you expected to be static called a request-time API (cookies(), headers(), searchParams, or an uncached fetch), so Next.js cannot prerender it; fix it by marking the route dynamic on purpose or by moving the request-time code behind a Suspense boundary.

The full message looks like this:

Error: Dynamic server usage: Route /dashboard couldn't be rendered statically because it used `cookies`.
See more info here: https://nextjs.org/docs/messages/dynamic-server-error

It is noisy in build logs and, worse, it sometimes hides a real production problem: a page you thought was cached is hitting your database on every request.

What Triggers It

Trigger Why it forces dynamic rendering Fix
cookies() / headers() Values only exist per request export const dynamic = 'force-dynamic'
searchParams prop Query string is per request Accept it, or move to a client component
fetch(url, { cache: 'no-store' }) Opts out of the data cache Add revalidate if data can be stale
draftMode() / auth helpers Read cookies internally Wrap in Suspense or mark dynamic

Fix 1: Declare the Route Dynamic On Purpose

If the page genuinely depends on the user, say so. The error disappears because Next.js no longer tries to prerender it.

// app/dashboard/page.tsx
import { cookies } from 'next/headers'

export const dynamic = 'force-dynamic'

export default async function Dashboard() {
  const session = (await cookies()).get('session')?.value
  // ...
}

In Next.js 15, cookies() and headers() are async, so remember the await. Forgetting it is a common source of confusing type errors right next to this one.

Fix 2: Push Request-Time Code Behind Suspense

Rather than making the whole page dynamic, isolate the personalized part. The static shell is served instantly and the dynamic piece streams in.

import { Suspense } from 'react'

export default function Page() {
  return (
    <>
      <MarketingHero />            {/* static */}
      <Suspense fallback={<Skeleton />}>
        <UserGreeting />           {/* reads cookies() */}
      </Suspense>
    </>
  )
}

This is the pattern that keeps TTFB low. If your pages are slow after switching everything to dynamic, see Slow TTFB in Next.js on Vercel.

Fix 3: Stop an Accidental no-store Fetch

A shared data helper with cache: 'no-store' makes every page that imports it dynamic. Search your codebase for it:

grep -rn "no-store" app/ lib/

If the data can be a minute old, replace it with fetch(url, { next: { revalidate: 60 } }). You get fresh-enough data and a static page.

Fix 4: Check Layouts and Middleware

One cookies() call in app/layout.tsx makes every route under it dynamic. Move it into the component that needs it. Also confirm middleware is not rewriting routes you assumed were static.

How to Verify the Fix

  1. Run next build and read the route table. ○ means static, ƒ means dynamic.
  2. Compare it against what you intended. Any ƒ you did not expect is a bug.
  3. Deploy and check response headers: x-vercel-cache: HIT on static pages.

Catch the Production Version of This Bug

The build-time error is the friendly version. The silent version is a page that is dynamic by accident and quietly doubles your database load or function bill. Relia learns what is normal for your app's requests and tells you when a route's latency jumps, with the likely cause and one next step, such as restoring static rendering or adding the missing Suspense boundary. Relia doesn't change code; you or your AI assistant make the fix. Pair it with the Next.js error monitoring guide to cover all runtimes.

FAQ

Is "Dynamic server usage" an error or a warning?

During next build it is logged as an error for static generation, but the build usually continues and the route simply becomes dynamic. Treat it as a signal that your caching assumption is wrong.

Does force-dynamic hurt performance? It disables prerendering for that route, so every request runs on the server. Use it only for user-specific pages and keep the rest static.

Why does it only fail on Vercel and not in next dev? next dev renders everything on demand and never prerenders. The error only appears when next build tries static generation, which is why you should run a production build before every deploy.

All guides