nextjsseverity: can-fix
DynamicServerError

Next.js Dynamic Server Usage Error: Route opted into dynamic rendering

DynamicServerError — route uses dynamic APIs in static context

97% fixable~10 mindifficulty: intermediate

Verified against Next.js 14.2 docs: Route Segment Config — dynamic option, Next.js 15.0 docs: Async Request APIs (Breaking Change), Next.js source: packages/next/src/server/app-render/dynamic-rendering.ts · Updated June 2026

> quick_fix

A route marked for static rendering called a dynamic API like cookies(), headers(), or accessed searchParams. Either add export const dynamic = 'force-dynamic' to opt the route into dynamic rendering, or move the dynamic API call into a Client Component that reads the value at runtime instead of build time.

// app/dashboard/page.tsx
// Option 1: Opt into dynamic rendering
export const dynamic = 'force-dynamic';

import { cookies } from 'next/headers';

export default async function Dashboard() {
  const cookieStore = await cookies();
  const token = cookieStore.get('session');
  return <div>Welcome back</div>;
}

What causes this error

Next.js App Router statically renders routes by default at build time. When Next.js encounters a call to cookies(), headers(), searchParams, or useSearchParams() during static generation, it throws a DynamicServerError because these APIs require an incoming HTTP request — they have no meaningful value at build time. The framework detects this conflict between the route's rendering strategy (static) and the API's requirements (dynamic) and halts the build. In Next.js 14, searchParams in page props was synchronous; in Next.js 15, it became an async Promise that must be awaited, which introduced additional confusion around this error.

> advertisementAdSense placeholder

How to fix it

  1. 01

    step 1

    Identify which dynamic API triggered the error

    The error message tells you which function caused the issue. Look for cookies(), headers(), searchParams access, or useSearchParams() in the route file and any Server Components it imports. The stack trace points to the exact file and line.

    # The build error looks like:
    # Error: Route /dashboard couldn't be rendered statically because it used `cookies`.
    # or: Dynamic server usage: Page couldn't be rendered statically because it used `headers`.
  2. 02

    step 2

    Decide: does this route need to be static or dynamic?

    If the route shows personalized content (user dashboards, authenticated pages), it should be dynamic — add the route segment config. If the route is mostly static but needs one small dynamic value (like a theme cookie), consider restructuring so the dynamic part lives in a Client Component instead.

  3. 03

    step 3

    Option A: Make the route dynamic with segment config

    Add export const dynamic = 'force-dynamic' at the top level of your page or layout file. This tells Next.js to skip static generation for this route and render it on every request.

    // app/profile/page.tsx
    export const dynamic = 'force-dynamic';
    
    import { headers } from 'next/headers';
    
    export default async function Profile() {
      const headerList = await headers();
      const userAgent = headerList.get('user-agent');
      return <div>UA: {userAgent}</div>;
    }
  4. 04

    step 4

    Option B: Move dynamic logic to a Client Component

    If you want the page shell to remain static, extract the dynamic part into a Client Component that reads cookies or search params on the client side. This preserves static generation for the rest of the page.

    // app/page.tsx — stays static
    import { ThemeSwitcher } from './theme-switcher';
    
    export default function Home() {
      return (
        <div>
          <h1>Welcome</h1>
          <ThemeSwitcher /> {/* reads cookie client-side */}
        </div>
      );
    }
    
    // app/theme-switcher.tsx
    'use client';
    import { useEffect, useState } from 'react';
    
    export function ThemeSwitcher() {
      const [theme, setTheme] = useState('light');
      useEffect(() => {
        const saved = document.cookie
          .split('; ')
          .find(c => c.startsWith('theme='));
        if (saved) setTheme(saved.split('=')[1]);
      }, []);
      return <button>Current: {theme}</button>;
    }
  5. 05

    step 5

    Handle searchParams correctly in Next.js 15

    In Next.js 15, searchParams is a Promise. If you access it synchronously, you get this error. Await it in the page component, or use useSearchParams() in a Client Component wrapped in Suspense.

    // Next.js 15 — searchParams is async
    export default async function SearchPage({
      searchParams,
    }: {
      searchParams: Promise<{ q?: string }>;
    }) {
      const { q } = await searchParams;
      return <div>Results for: {q}</div>;
    }

How to verify the fix

  • npm run build completes without DynamicServerError.
  • The route renders correctly in production (npm run start) with dynamic data populated.
  • Static routes still generate HTML files in .next/server/app/ — only the intended routes are dynamic.

When you’d actually want this

In Next.js 15, cookies() and headers() also became async and must be awaited. Calling them without await triggers a different but related error. If you see 'cookies is not a function' after upgrading to Next.js 15, you need to add await before the call.

Why DynamicServerError happens at the runtime level

Next.js App Router applies Static Rendering by default during next build. When the renderer encounters a call to cookies(), headers(), or an access to the searchParams prop, it recognizes these as Request-scoped APIs — they can only produce meaningful values when an actual HTTP request is being processed. The static renderer has no request context, so it throws a DynamicServerError to prevent generating an HTML page with empty or undefined request-dependent data. This is an intentional build-time safety check, not a runtime crash.

Common debug mistakes for DynamicServerError

  • Importing a utility module that calls headers() deep in its call chain — the page file looks clean but an indirect dependency triggers dynamic rendering, and the error message only shows the top-level route.
  • Using searchParams without awaiting it in Next.js 15 — the prop changed from a plain object to a Promise, and synchronous access throws this error even though the same code worked in Next.js 14.
  • Adding cookies() to a root layout to read a theme preference — this forces every single route in the app to become dynamic, destroying static generation for the entire site.
  • Confusing 'force-static' with 'auto' — setting force-static and then calling cookies() produces this error because you explicitly told Next.js to keep the route static while using a dynamic API.

When DynamicServerError signals a deeper problem

This error exposes a fundamental architectural tension in the App Router: the default rendering strategy (static) conflicts with common patterns like authentication checks, theme preferences, and A/B testing that require request-time data. Most developers coming from the Pages Router expect every page to have access to request data because getServerSideProps made it explicit. In the App Router, the static-by-default model means you must consciously decide which routes need request access. The real solution is not just adding force-dynamic everywhere — it is designing your component tree so that static shells wrap dynamic leaves, maximizing cache hits while still delivering personalized content.

Editor's take

DynamicServerError is the App Router's way of telling you that you mixed two incompatible intentions: you wanted a page generated at build time, but you also wanted to read something that only exists when a real user makes a real request. The fix is always the same fork in the road — either make the route dynamic or push the dynamic logic to the client.

The mistake most teams make is reaching for force-dynamic as a blanket fix. It works, but it quietly converts your statically-generated marketing pages into server-rendered pages that hit your infrastructure on every request. One cookies() call in a root layout, and suddenly your entire app is dynamically rendered. Your hosting bill goes up, your Time to First Byte gets worse, and your CDN cache hit rate drops to zero — all because someone wanted to read a theme cookie.

The better pattern is the static shell, dynamic island approach. Keep your page component static. Move the cookie/header reading into a Client Component that hydrates after the static HTML loads. The user sees the page instantly (static), and the personalized bits fill in a moment later (client-side). This is exactly what React Server Components were designed to enable.

Next.js 15 made this error more common by converting cookies(), headers(), and searchParams to async APIs. Code that worked fine in Next.js 14 suddenly throws during the upgrade. The fix is straightforward — add await — but the volume of affected files in a large codebase can be significant. Run the official codemod (npx @next/codemod@latest next-async-request-api) to handle the migration automatically.

By Bikram Nath · Curator · Updated June 2026

Frequently asked questions

What is the difference between 'force-dynamic' and 'force-static'?

export const dynamic = 'force-dynamic' renders the route on every request (SSR). export const dynamic = 'force-static' forces static generation and will error if dynamic APIs are used. The default 'auto' lets Next.js decide — it tries static first and falls back to dynamic if it detects dynamic API usage. Use 'force-dynamic' when you know the route needs request-time data.

Can I use cookies() in a layout without making all child routes dynamic?

No. If a layout calls cookies() or headers(), every route that uses that layout becomes dynamic. Layouts are shared across routes, so a dynamic layout forces dynamic rendering on all its children. If only some child routes need cookies, move the cookies() call into those specific page files instead of the shared layout.

Why did this work in the Pages Router but fails in the App Router?

The Pages Router used getServerSideProps to explicitly mark pages as server-rendered. In the App Router, there is no getServerSideProps — the framework auto-detects whether a route is static or dynamic based on which APIs you use. If you use a dynamic API without explicitly opting into dynamic rendering, the build fails. The App Router defaults to static, while the Pages Router defaulted to on-demand rendering.

disclosure:Errordex runs AdSense, has zero third-party affiliate or sponsored links, and occasionally links to the editor’s own paid digital products (clearly labelled). Every fix is cross-referenced against the official sources listed in the “sources” sidebar before it ships. If a fix here didn’t work for you, please email so we can update the page.