vercel/next.js · error · Error
Page with dynamic = "error" encountered dynamic data method
Error message
Page with dynamic = "error" encountered dynamic data method on ${path}. What it means
Thrown in the App Router export path when a route declared `export const dynamic = 'error'` (a.k.a. the page opts out of dynamic rendering) yet, during prerender, the render result reports cacheControl.revalidate === 0 AND isDynamicError is true. The dynamic='error' contract promises the route is fully static; the framework detects that a dynamic data method (cookies(), headers(), draftMode(), searchParams sync access, request.time, etc.) was invoked and refuses to silently fall back to dynamic rendering.
Source
Thrown at packages/next/src/export/routes/app-page.ts:115
flightData,
cacheControl = { revalidate: false, expire: undefined },
postponed,
fetchTags,
fetchMetrics,
segmentData,
prefetchHints,
renderResumeDataCache,
hasPendingUi,
} = metadata
// Ensure we don't postpone without having PPR enabled.
if (postponed && !renderOpts.experimental.isRoutePPREnabled) {
throw new Error('Invariant: page postponed without PPR being enabled')
}
if (cacheControl.revalidate === 0) {
if (isDynamicError) {
throw new Error(
`Page with dynamic = "error" encountered dynamic data method on ${path}.`
)
}
const { staticBailoutInfo = {} } = metadata
if (debugOutput && staticBailoutInfo?.description) {
logDynamicUsageWarning({
path,
description: staticBailoutInfo.description,
stack: staticBailoutInfo.stack,
})
}
return { cacheControl, fetchMetrics }
}
// If page data isn't available, it means that the page couldn't be rendered
// properly so long as we don't have unknown route params. When a route doesn'tView on GitHub (pinned to 0ae8c72462)
Solutions
- Remove `export const dynamic = 'error'` from the route if it genuinely needs request data, or switch to `dynamic = 'force-dynamic'`.
- Push the dynamic access (cookies/headers/searchParams) into a child segment or Suspense boundary that is allowed to be dynamic, keeping the parent static.
- If the dynamic call is unintentional, locate it via the build's 'reason' log and replace it with a static equivalent (e.g. read from generateStaticParams params, not from cookies).
- Verify no imported library implicitly accesses request storage; wrap it behind a client component or a 'use cache' boundary.
Example fix
// before — app/page.tsx
import { cookies } from 'next/headers'
export const dynamic = 'error'
export default function Page() {
const theme = cookies().get('theme')?.value
return <p>{theme}</p>
}
// after — drop the conflicting contract
import { cookies } from 'next/headers'
export const dynamic = 'force-dynamic'
export default function Page() {
const theme = cookies().get('theme')?.value
return <p>{theme}</p>
} Defensive patterns
Strategy: validation
Validate before calling
// Scan the route module's source for dynamic APIs before declaring dynamic='error'
const DYNAMIC_APIS = ['cookies(', 'headers(', 'draftMode(', 'searchParams', 'request\.']
function safeToUseDynamicError(source: string): boolean {
return !DYNAMIC_APIS.some((re) => new RegExp(re).test(source))
} Type guard
// runtime check: a page declaring dynamic='error' must not touch request storage
import { isCallerDynamicAccessSupported } from './checks'
function assertStaticContract(usesDynamic: boolean, declared: string) {
if (usesDynamic && declared === 'error') {
throw new Error('Route uses dynamic APIs but declares dynamic="error"')
}
} Try / catch
// Not catchable in user code — runs at build time.
// Instead, gate the contract with a CI grep:
// rg -n "cookies\(|headers\(" app/**/page.tsx | xargs grep -l "dynamic.*error" Prevention
- Only set dynamic='error' on routes you have audited for cookies/headers/searchParams usage.
- Keep dynamic data access in dedicated child segments behind Suspense.
- Add an ESLint custom rule banning cookies()/headers() in routes that export dynamic='error'.
- Review third-party imports for implicit request-storage access.
When it happens
Trigger: An app-router page or layout exports `const dynamic = 'error'` and then calls cookies(), headers(), searchParams, or another dynamic request API during render or in a generateMetadata/generateStaticParams helper. Triggered at build time during prerender of that route.
Common situations: Developers copy a server component that reads cookies() for personalization but also leave `dynamic = 'error'` from a template. Or a third-party dependency (auth, A/B testing) reaches into request storage. Upgrading Next.js can newly flag a previously-tolerated access because the dynamic-usage detector tightened.
Related errors
- Default export is missing in {resource_path}
- No router instance found. You should only use "next/router"
- Error for page ${page}: ${SERVER_PROPS_EXPORT_ERROR}
- Command failed: ${command} ${args.join(' ')} (exit ${code})
- Missing ${NEXT_BIN}. Build Next.js first (pnpm --filter=next
AI-assisted analysis of vercel/next.js@0ae8c72462 (2026-08-06).
Data as JSON: /api/errors/d62a9048c6e9b55d.
Report an issue: GitHub.