Why is my Next.js page dynamic?

After building a Next.js project it will provide you with a nice overview of all the pages it built. At times you might look surprised to see some pages marked as dynamic. Next.js contains a number of small implicit rules that determine whether a page is dynamic or not. To save you and me some time here they are (as of Next.js version 13 and 14):

Dynamic params

It's possible to define parameters in route paths, such as [id] in a route named /post/[id]. Because Next.js cannot figure out all the possible values of id it will dynamically render the page when a request comes in. To avoid this you can define a generateStaticParams function and export it from the page. This function needs to return an array of all the possible values of id.

But just using generateStaticParams is not entirely enough. Next.js might encounter a value being passed that was not found in the array while the server is running and attempt to render it at runtime. You can force it to accept only the values that you supply by exporting a variable named dynamicParams and setting it to false.

See docs

// This is optimal
export const dynamicParams = false
export async function generateStaticParams() {
  return [
    {id: 'a'},
    {id: 'b'}
  ]
}

searchParams (server side)

The second argument of a page server component contains the search params from the url. Simply accessing it triggers the dynamic flag.

See docs

 

useSearchParams (client side)

Using the useSearchParams hook in a client side component without a Suspense boundary triggers dynamic.

See docs


cookies & headers

Accessing cookies() or headers() inside a Server component triggers dynamic.

See docs

import {cookies} from 'next/headers'
cookies().get('test') // This line triggers dynamic

Fetch cache

Using a value of 'no-store' or 'no-cache' in the options you pass to the fetch() function will trigger dynamic.

Using a value of {next: {revalidate: 0}} will also trigger dynamic.

See docs

await fetch('https://example.com', {
  cache: 'no-store' // This line triggers dynamic
})

draftMode

A special mention to draftMode which does not trigger dynamic. You can safely call and check the isEnabled flag and your page will still be built statically.

import {draftMode} from 'next/headers'
draftMode().isEnabled // This is fine

See docs

Forcing dynamic mode

If your page shows up as static but you want to force data to be fetched whenever a request comes you can export the dynamic setting with a value of 'force-dynamic'.

See docs

export const dynamic = 'force-dynamic'

Erroring on dynamic

If you want to be sure a page is only ever rendered statically you can add the dynamic setting with a value of 'error'. This will provide you with a warning at build time if any of the above conditions are met.

See docs

export const dynamic = 'error'