Debug dynamic rendering by checking for dynamic APIs, fetch cache options, route configuration, headers/cookies usage, and using Next.js debugging tools like route logs and build analysis
When a page that should be static is rendering dynamically, it's usually because Next.js has detected something that requires dynamic rendering—either a dynamic API call, a fetch without proper caching, or route configuration that forces dynamic behavior. Debugging this requires a systematic approach: first verify what rendering mode the page is actually using, then identify what's triggering the dynamic behavior, and finally apply the appropriate fix. Next.js provides several built-in tools and logs to help with this diagnosis.
Check build output: Run next build and look for the page in question—static pages are marked with '●', dynamic pages with 'λ' in the route table .
Add route logging: Enable logging: { fetches: { fullUrl: true } } in next.config.js to see detailed fetch behavior during rendering .
Inspect the page source: View the HTML source in browser—static pages have complete HTML, dynamically rendered pages may have less content or loading states .
Check for dynamic APIs: Look for usage of cookies(), headers(), searchParams, or noStore() which force dynamic rendering .
Review fetch options: Ensure fetch calls use { cache: 'force-cache' } or { next: { revalidate } } rather than { cache: 'no-store' } .
cookies(): Using cookies() from 'next/headers' anywhere in the page or its imported components forces dynamic rendering
headers(): Similarly, headers() access makes the page dynamic
searchParams: Accessing searchParams in a page component (not just passing to children) forces dynamic rendering
noStore(): Calling noStore() from 'next/cache' explicitly opts out of static caching
dynamic = 'force-dynamic': Page or layout exports export const dynamic = 'force-dynamic'
fetch with no-store: Any fetch using { cache: 'no-store' } or { next: { revalidate: 0 } } triggers dynamic behavior
Next.js allows explicit configuration of rendering modes via exported variables. Check if your page or any parent layout has exports that force dynamic behavior. Look for export const dynamic = 'force-dynamic', export const revalidate = 0, or export const fetchCache = 'force-no-store'. These can be inherited from parent layouts, so check the entire route segment chain. Also verify that you haven't accidentally set export const runtime = 'edge' which may have different caching semantics .
Add console.log statements in development: Run next dev and watch server logs for clues about when and how pages render .
Use next build --debug for detailed build output showing why each page was rendered as static or dynamic .
Install @next/bundle-analyzer to see what's included in your page bundles—unexpected client components can sometimes indicate hydration issues .
Check the Network tab in DevTools: Static pages return HTML immediately; dynamic pages may show loading states or API calls .
Use the React DevTools profiler to see if components are rendering on server or client .
Based on community experience, here are the most common reasons pages become dynamic: 1) Using cookies() or headers() without wrapping in Suspense; 2) Fetch calls with cache: 'no-store' (often copied from examples); 3) searchParams access in page components; 4) Parent layouts with force-dynamic; 5) Using dynamic APIs in imported components; 6) next/headers imports in client components (this throws errors but can cause confusion); 7) Missing generateStaticParams for dynamic routes combined with dynamicParams: false .
Wrap dynamic API usage in Suspense boundaries—this keeps the parent page static while allowing dynamic parts to stream .
Replace cache: 'no-store' with appropriate ISR revalidate values (e.g., next: { revalidate: 3600 }) .
Remove force-dynamic from layouts and pages unless absolutely necessary .
If you need cookies for personalization, consider using middleware to rewrite to pre-rendered static variants instead of accessing cookies in the page .
For searchParams, either accept the dynamic behavior (if truly needed) or move dynamic logic to client components .
Add generateStaticParams to pre-render known paths, and ensure dynamicParams is set appropriately .