Static vs Dynamic Handlers

Understanding when Route Handlers are cached.

Next.js4 min readConcept 46 of 65

Aggressive Caching

By default, Next.js will statically evaluate GET Route Handlers during the build process.

If you fetch data from a database inside a standard GET handler and return it, that data will be frozen at build time. It will not update when users request the endpoint.

Performance vs Freshness

Static handlers are incredibly fast because they just return a pre-generated JSON string from the CDN.

However, APIs usually need to respond with fresh data (e.g., the latest user profile). In these cases, you must opt out of caching to make the handler Dynamic.

Opting into Dynamic Rendering

POST, PUT, PATCH, and DELETE handlers are *always* dynamic.

To make a GET handler dynamic, you must do one of three things:

1. Use a dynamic function (like cookies() or headers()).

2. Access the URL's query string (request.nextUrl.searchParams).

3. Explicitly export const dynamic = 'force-dynamic' from the file.

The Cache Race

Fire requests at both the Static GET endpoint and the Dynamic GET endpoint. Watch how the Static endpoint always returns the exact same build timestamp, while the Dynamic one updates in real-time.

Cached (Static)

Default Behavior
/api/time-static/route.ts
// No dynamic functions used. Evaluated at build time.
export async function GET() {
const time = new Date().toLocaleTimeString();
return NextResponse.json({ time });
}
Server Response{ "time": "" }

Uncached (Dynamic)

Opt-in Behavior
/api/time-dynamic/route.ts
// Explicitly opted out of caching
export const dynamic = 'force-dynamic';
export async function GET() {
const time = new Date().toLocaleTimeString();
return NextResponse.json({ time });
}
Server Response{ "time": "" }

Check yourself

Pick an answer to lock it in, then read why. Getting one wrong is part of how it sticks.

  1. 1Which of the following will NOT force a `GET` Route Handler to become dynamic?

Remember this

  • GET Route Handlers are statically cached by default.
  • POST and DELETE handlers are always dynamic.
  • Reading cookies(), headers(), or searchParams opts a GET handler into dynamic rendering.
  • You can explicitly opt out of caching with export const dynamic = 'force-dynamic'.

Done with this concept?

Mark it complete to track your progress. No login needed.