Route Segment Config and Runtimes
The idea in one sentence
Section titled “The idea in one sentence”Route Segment Config is a set of file-level export const values that let you override how a single route segment renders, caches, and which runtime it runs on.
The segment config exports
Section titled “The segment config exports”Each of these is a named export at the top of a layout.tsx, page.tsx, or route.ts. They apply to that segment and its children.
export const dynamic = 'auto'; // 'auto' | 'force-dynamic' | 'error' | 'force-static'export const revalidate = 3600; // ISR: rebuild this route at most once per hourexport const runtime = 'nodejs'; // 'nodejs' (default) | 'edge' (deprecated)export const fetchCache = 'auto'; // control the default caching of fetch() in this segmentexport const dynamicParams = true; // allow params not returned by generateStaticParamsdynamic— force the rendering mode;'force-dynamic'renders every request,'force-static'forbids dynamic APIs.revalidate— seconds between background rebuilds; this is how you turn on ISR.runtime— the execution environment,'nodejs'(default) or'edge'(deprecated in Next.js 16).fetchCache— override howfetch()calls in the segment are cached by default.dynamicParams— whenfalse, a param not listed bygenerateStaticParamsreturns a 404 instead of being rendered on demand.
Node.js runtime vs Edge runtime
Section titled “Node.js runtime vs Edge runtime”Every route runs on the default Node.js runtime, which gives you the full Node API surface. The Edge runtime is a smaller, Web-API-only environment. Since Next.js 16 it is deprecated as a route runtime: next build prints The Edge Runtime is deprecated for any page that exports it, and Cache Components rejects the export outright — for either value, not just 'edge' (proven below). Edge lives on for proxy.ts (the file formerly known as Middleware).
// Opt a route into the Edge runtime — deprecated since Next.js 16: next build warns, Cache Components rejects itexport const runtime = 'edge';What that means in practice:
- Node.js (default) — full Node APIs, native modules, larger libraries, database drivers, and the only runtime that supports ISR and Cache Components. Leave the
runtimeexport out and this is what you get. - Edge — a limited API set (no filesystem, no native modules, no
require), no ISR, and a deprecation warning at build. If you need edge-style logic such as redirects or lightweight auth checks, put it inproxy.tsrather than on a route.
ISR through revalidate
Section titled “ISR through revalidate”Incremental Static Regeneration keeps a route static and fast while still letting it go stale and refresh on a timer. Set revalidate to a number of seconds and Next.js serves the cached static page, then rebuilds it in the background after that window.
export const revalidate = 60;
export default async function Products() { const products: { id: number; name: string }[] = await fetch('https://api.example.com/products').then((r) => r.json()); return <ul>{products.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;}The first request after 60 seconds triggers a background rebuild; visitors keep getting the fast cached page until the fresh one is ready.
Under cacheComponents: true the revalidate export is rejected. The equivalent is a 'use cache' scope with a cacheLife profile — next build then lists the route as ○ /products 1m 1h:
import { cacheLife } from 'next/cache';
export default async function Products() { 'use cache'; cacheLife('minutes'); // stale 5m, revalidate 1m, expire 1h — closest preset to revalidate = 60 const products: { id: number; name: string }[] = await fetch('https://api.example.com/products').then((r) => r.json()); return <ul>{products.map((p) => <li key={p.id}>{p.name}</li>)}</ul>;}Every export, proven against a real build
Section titled “Every export, proven against a real build”Every incompatible export produces the exact same shape of error under cacheComponents: true — confirmed one at a time in the probe, real messages, only the file path trimmed:
Error: Route segment config "dynamic" is not compatible with `nextConfig.cacheComponents`. Please remove it.Error: Route segment config "revalidate" is not compatible with `nextConfig.cacheComponents`. Please remove it.Error: Route segment config "fetchCache" is not compatible with `nextConfig.cacheComponents`. Please remove it.Error: Route segment config "dynamicParams" is not compatible with `nextConfig.cacheComponents`. Please remove it.Error: Route segment config "runtime" is not compatible with `nextConfig.cacheComponents`. Please remove it.Error: Route segment config "experimental_ppr" is not compatible with `nextConfig.cacheComponents`. Please remove it.preferredRegion and maxDuration are not on that list — built with cacheComponents: true and both exports present, the build succeeds. preferredRegion prints its own, unrelated deprecation warning (it’s deprecated everywhere, with or without Cache Components); maxDuration builds silently, with no warning at all:
⚠ The "preferredRegion" route segment config is deprecated. Learn more: https://nextjs.org/docs/messages/preferred-region-deprecated| Export | Under Cache Components | Equivalent |
|---|---|---|
dynamic = 'force-dynamic' | Rejected — build error | Not needed; every route is dynamic by default. Wrap the parts that need prerendering in 'use cache' instead. |
dynamic = 'force-static' | Rejected — build error | 'use cache' with a long cacheLife (e.g. 'max') for the uncached data; move any request-time API into a <Suspense> boundary. |
revalidate | Rejected — build error | cacheLife(...) inside a 'use cache' scope — pick the closest built-in profile ('seconds'…'max') or define a custom one. |
fetchCache | Rejected — build error | Not needed — every fetch() inside a 'use cache' scope is cached automatically. |
dynamicParams = false | Rejected — build error | Call notFound() inside the page when a param that wasn’t returned by generateStaticParams doesn’t resolve to real data. |
runtime ('nodejs' or 'edge') | Rejected — build error, either value | None — the export itself is unsupported. Cache Components requires the Node.js runtime; there is nothing left to configure. |
preferredRegion | Builds fine — unrelated deprecation warning only | None — deprecated independently of Cache Components. Remove it regardless and let the deployment platform pick a region. |
maxDuration | Builds fine — no warning | None needed — it’s a deployment-platform execution-time hint, orthogonal to caching, and stays exactly as it was. |
experimental_ppr = true | Rejected — build error | Not needed; PPR is the default behavior the moment cacheComponents: true is set. |
flowchart TD A[Route segment config] --> B[dynamic] A --> C[revalidate for ISR] A --> D[runtime] D --> E[nodejs, full APIs, default] D --> F[edge, deprecated in 16, limited APIs, no ISR]
Mental Model
Section titled “Mental Model”Route Segment Config is a set of dials on the outside of a file — you never touch the render body to change caching or runtime, you just add an export const at the top. Cache Components removes every one of those dials except two (preferredRegion, maxDuration, both deployment hints that have nothing to do with caching) and replaces the rest with decisions made inside the function body instead — 'use cache' and cacheLife(...) sit next to the code they affect, rather than declaring a blanket policy for the whole file from outside it.
Trade-offs
Section titled “Trade-offs”| What you get | Benefit | Cost |
|---|---|---|
| Route Segment Config (previous model) | One line per concern, declared at the top of the file, easy to scan | A blanket, file-wide setting — can’t cache one fetch differently from another in the same segment |
'use cache' + cacheLife (Cache Components) | Caching decisions live next to the code they cache, at whatever granularity you choose (function, component, page) | More to read inside the function body; no single top-of-file summary of the segment’s caching policy |
Leaving runtime unset | Full Node.js API surface, compatible with Cache Components | Can’t opt into Edge’s smaller cold-start footprint on a route (Edge is deprecated anyway) |
Removing preferredRegion/maxDuration when not needed | One less config surface to keep in sync with deployment reality | None — these two never conflicted with Cache Components in the first place |
Common Misconceptions
Section titled “Common Misconceptions”- Cache Components only rejects
runtime = 'edge', since that’s the deprecated value. It rejects theruntimeexport itself, for either value —runtime = 'nodejs'fails exactly the same way, because Cache Components requires Node.js and there’s nothing left to configure. preferredRegionandmaxDurationmust also be migrated to'use cache'/cacheLife. They’re not caching config at all — they’re deployment-platform hints (region, execution time limit) and Cache Components doesn’t touch them.preferredRegionis deprecated for unrelated reasons;maxDurationisn’t deprecated at all.dynamicParams = falsehas no replacement under Cache Components. It does — callnotFound()inside the page itself when an unlisted param doesn’t correspond to real data, rather than declaring the 404 behavior as a blanket file-level export.- Removing
experimental_ppr = trueturns PPR off. The opposite — that export (and the matchingexperimental.pprconfig flag) was removed because PPR became the default behavior ofcacheComponents: true; there’s no way to havecacheComponentson without PPR active.
Reading AI-generated code
Section titled “Reading AI-generated code”When a generated snippet exports Route Segment Config, check for these before trusting it:
- Does the project have
cacheComponents: trueinnext.config.ts, while a page still exportsdynamic,revalidate,fetchCache,dynamicParams,runtime, orexperimental_ppr? Every one of those fails the build outright — this is a build-time signal, not a subtle bug, but AI-generated code is often written (or copy-pasted from an older example) without checking the project’s actual config. - Does the code set
runtime = 'nodejs'explicitly, assuming that’s always harmless because it’s the default? Under Cache Components, exporting it at all — even the default value — is rejected. - Does
dynamicParams = falseappear anywhere in a Cache Components project? It needs to become anotFound()call inside the component instead. - Is
preferredRegionormaxDurationbeing “migrated” tocacheLifeor'use cache'unnecessarily, when neither one is actually incompatible with Cache Components? - Does old documentation or a stale example still reference
experimental.pprorexperimental_ppras if it needs to be set for PPR to work in Next.js 16? Both were removed — PPR is on whenevercacheComponents: trueis.
🐛 Spot the bug
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {cacheComponents: true,}
export default nextConfig
// app/dashboard/page.tsxexport const dynamic = 'force-dynamic'
export default function Dashboard() {return <div>Dashboard</div>}Show the answer
cacheComponents: true and export const dynamic = 'force-dynamic' cannot coexist. The build fails with Route segment config "dynamic" is not compatible with nextConfig.cacheComponents. Please remove it. — under Cache Components every route is already dynamic by default, so force-dynamic isn’t just redundant, it’s a rejected export. The fix is to delete the dynamic export entirely; if part of the page genuinely needs to be static (or cached), wrap that part in 'use cache' instead of declaring a file-wide mode from outside the component.