In the Next.js App Router, how can you tell whether a GET route handler runs on every request or is prerendered and cached at build time, and what decides which one you get?
answer
- the default moved between majors
- segment config exports decide it
- force-static blanks cookies and headers
- the build output labels each route
- only GET is ever eligible
basics
~20 sIn Next.js 15 and 16 a GET route handler runs on every request unless you opt in with export const dynamic = 'force-static'; Next.js 14 and earlier cached them by default. The build output labels each route static or dynamic.
solid answer
~50 sTwo things decide it: the segment config you export, and whether the handler touches request-time input. In Next.js 15 and 16 GET route handlers are dynamic by default — every request runs the function. Exporting `export const dynamic = 'force-static'` makes Next evaluate the handler at build time and serve the cached response, and under `force-static` `cookies()` and `headers()` return empty values, so a handler that depends on them silently sees nothing. `export const revalidate = 60` puts a time-based lifetime on that cached output. Only `GET` is ever eligible; `POST`, `PATCH` and `DELETE` always execute per request. The practical check is `next build`, whose route table lists each path as static or dynamic, so you can see which bucket a handler landed in before shipping. Pin the version before asserting any default — Next.js 14 and earlier cached GET handlers automatically, which is where most stale-endpoint stories come from.
code
typescript · 9 lines// app/api/changelog/route.ts
export const dynamic = 'force-static'
export const revalidate = 3600
export async function GET() {
const res = await fetch('https://example.com/changelog.json')
const entries = await res.json()
return Response.json({ entries })
}go deeper
Know that a route handler's response can be produced once at build time instead of on every request, and that the development server never caches, so local behaviour proves nothing about production.
Explain the controls: the dynamic and revalidate segment exports, that only GET is eligible, and that reading request-time input is what keeps a handler executing per request.
Diagnose it in production: read the build output's static-versus-dynamic labels, pin the Next.js major before asserting a default, and recognise force-static blanking cookies and headers as a silent-wrong-answer failure rather than a crash.
Own the policy — which endpoints may be cached at all, how freshness requirements are written down rather than assumed, and how the team avoids depending on a framework default that has already flipped once between majors.
## The question behind the question An interviewer asking this is checking two things: that you know a route handler's response can be produced once at build time rather than per request, and that you do not recite a caching default without knowing which Next.js major it belongs to. The default has moved, and a candidate who states one flatly is usually quoting a blog post from the wrong year. ## The default moved between majors In Next.js 14 and earlier, a `GET` route handler was cached by default when it looked cacheable — no request object read, no dynamic function called, a plain `Response` returned. Teams hit this constantly: an endpoint that queried a database returned identical JSON forever in production while behaving perfectly in `next dev`, because the development server does not cache. Next.js 15 reversed it, and 16 keeps the reversal: `GET` route handlers are **not** cached by default. Each request executes the handler. Caching is now something you ask for. The defensible way to answer in an interview is to name the version you are assuming and describe the mechanism rather than the default: what the cache keys on, what forces it off, and how you opt in. ## What keeps a handler per-request Anything that only exists when a real request arrives: - reading `request.nextUrl`, `request.cookies`, `request.headers`, or the body - calling `cookies()`, `headers()` or `draftMode()` from `next/headers` - exporting `export const dynamic = 'force-dynamic'` - being any method other than `GET` The first group is not a rule Next invented — it is arithmetic. A handler evaluated at build time has no request to read, so it cannot depend on one. ## Opting into cached output ```ts // app/api/changelog/route.ts export const dynamic = 'force-static' export async function GET() { const res = await fetch('https://example.com/changelog.json') return Response.json(await res.json()) } ``` `force-static` tells Next to evaluate the handler during the build and serve the stored response afterwards. The sharp edge is what it does to the dynamic APIs: under `force-static`, `cookies()` and `headers()` return **empty values** rather than throwing. A handler that branches on a session cookie does not crash — it quietly takes the signed-out branch and bakes that answer into the cached response for everyone. This is the silent-wrong-answer failure mode, and it is why `force-static` belongs only on handlers that genuinely take no request-time input. `export const revalidate = 3600` attaches a lifetime to that cached output, so it is regenerated on demand after the window elapses instead of being frozen at the build. `export const dynamic = 'force-dynamic'` is the explicit opposite — never cache, always run. Both exports must be plain, statically analysable `const`s. A value computed from an environment variable at module scope will not be picked up as segment config. ## How to actually check Run `next build` and read the route table it prints. Every path is listed with a marker saying whether it was prerendered as static content or will be server-rendered on demand, and the legend at the bottom spells the markers out. That table is the ground truth: it tells you what the build decided, rather than what you believe the defaults to be. Checking it is a two-second habit that catches an accidentally-static endpoint before a user does. Do not use `next dev` as evidence. The development server re-runs handlers on every request by design, so a stale-in-production bug is invisible locally — which is exactly why it reaches production. ## Not the same thing as HTTP caching Next's route-handler caching is about whether *your function runs*. Response headers govern what browsers and shared caches downstream do with the bytes afterwards. They are independent layers: a per-request handler can still emit a long-lived cache header, and a statically evaluated one can be told not to be stored downstream. When diagnosing staleness, establish which layer is holding the old copy before changing anything, because the fixes are unrelated. ## Diagnosing a stale endpoint The sequence that works: confirm the Next.js major; grep the file for `dynamic` and `revalidate` exports; look at the build output's label for that path; and only then look downstream. On 15 or 16, a static label on a handler you expected to be dynamic means someone wrote `force-static` — often to fix a build error caused by something else. On 14, no exports at all is enough to produce the symptom, and the fix is to opt out explicitly rather than to rely on the handler looking dynamic.
- A GET route handler serves stale JSON in production but fresh data locally. Where do you look first?The Next.js major and the segment config. On 14 the handler was cached by default; on 15 or 16 someone has exported `dynamic = 'force-static'` or a `revalidate` value. Then read the `next build` route table: if that path is labelled static, the response you are seeing was produced once at build time. Development mode never caches, which is why local looks fine.
- What does export const dynamic = 'force-static' do to cookies() and headers() inside the handler?They return empty values rather than throwing. The handler is evaluated without a real request, so a cookie or header read yields nothing and the handler quietly builds a response from missing input — then caches that answer for everyone. It is a silent wrong answer, not a crash, which makes it worth calling out explicitly in review.
- Are POST or PATCH route handlers ever cached the same way?No. Only `GET` handlers are eligible for Next's route-handler caching; every other method executes per request. That is why a mutation endpoint never needs an opt-out, and why the stale-response failure mode only ever shows up on read endpoints.
saying these in an interview costs you the question
- States a caching default without naming the Next.js version
- Thinks force-static makes cookies() throw rather than return empty
- Believes POST handlers are cached like GET handlers
- Confuses Next's route cache with downstream HTTP caching
- Treats dev-server behaviour as proof of production behaviour