Caching Route Handlers
Caching Route Handlers
Level 7 — API & Route Handlers The specific rules and behaviors that determine when a Next.js
route.tsAPI endpoint caches its response at build-time versus calculating it dynamically on every request.
1. Prerequisites
- Route Handlers (
route.ts) — The endpoints being cached. - Data Caching (
force-cache,no-store) — Similar caching concepts, but applied to the entire API route rather than a singlefetch.
2. Term Category
Server & Edge API (Route Handler Caching Behavior): Route Handlers are cached statically by default when using GET methods without dynamic request inspection.
3. Explanation
Environment Context
- Server Only (Build-Time & Request-Time)
(1) Design Motivation — "Why did we design this?"
If you build an API endpoint GET /api/products that just returns a static JSON list of products from a file, it is extremely inefficient for the server to read that file and generate the JSON 10,000 times for 10,000 visitors.
Next.js optimizes Route Handlers by aggressively evaluating them at Build Time. If it determines the endpoint is static, it caches the JSON response forever. When a user requests the endpoint, Next.js just serves the cached string instantly.
Understanding how Next.js decides if a route is Static (cached) or Dynamic (not cached) is critical.
(2) The Rules of Static vs Dynamic
By default, GET requests are cached (Static).
However, Next.js will automatically switch your GET route to Dynamic (re-evaluated on every request) if it detects you doing any of the following:
- Using the
Requestobject (e.g., reading headers or URL search params). - Using dynamic functions like
cookies()orheaders(). - Using dynamic folder segments (e.g.,
app/api/users/[id]/route.ts). - Using an HTTP method other than
GET(e.g.,POST,PUT,DELETEare NEVER cached).
(3) Forcing Dynamic Behavior
If your GET route doesn't use the Request object or cookies, but it checks a database that changes frequently, Next.js will incorrectly cache it forever!
You must manually force it to be dynamic using Route Segment Config.
// app/api/time/route.ts
// This forces Next.js to run this route dynamically on every request!
export const dynamic = 'force-dynamic';
export async function GET() {
// If we didn't export `force-dynamic`, this time would be permanently
// frozen at whatever time the `npm run build` command was executed!
return Response.json({ time: new Date().toISOString() });
}
4. Common Mistakes & Pitfalls
Mistake 1: Stale API Responses in Production
The mistake: A developer writes a GET Route Handler that queries a database to get a list of active users. They test it in development (npm run dev) and it updates perfectly. They deploy it to production, and the API always returns the exact same list of users forever.
Why it's wrong: In Development mode, caching is largely disabled so you can see your changes. But during the Production Build (npm run build), Next.js looks at the route, sees no Request usage, and caches the database result statically.
Golden Rule: If a GET Route Handler returns data that can change independently of a code deployment (like database queries), you MUST add export const dynamic = 'force-dynamic'; or export const revalidate = 0; to the file!
Mistake 2: Assuming POST, PUT, or DELETE Route Handlers Are Cached Automatically
The mistake: Expecting a POST /api/orders Route Handler response to be cached in Next.js Data Cache.
Why it's wrong: Next.js caches ONLY GET Route Handlers. Non-GET HTTP methods (POST, PUT, DELETE) are ALWAYS evaluated dynamically on every request.
Incorrect:
/* Expecting POST route handler responses to be cached */
Fix:
/* GET route handlers are cached by default; POST/PUT/DELETE are always dynamic */
Mistake 3: Forgetting export const dynamic = 'force-dynamic' on GET Handlers Reading DB State
The mistake: Creating a GET Route Handler app/api/users/route.ts executing database queries without disabling static caching.
Why it's wrong: By default, GET Route Handlers returning static responses are evaluated and cached at BUILD TIME. New database entries added in production will NOT appear unless force-dynamic is set.
Incorrect:
// app/api/users/route.ts
export async function GET() {
const users = await db.user.findMany(); // ❌ Evaluated once at BUILD time!
return Response.json(users);
}
Fix:
// app/api/users/route.ts
export const dynamic = 'force-dynamic'; // Enforce dynamic request-time evaluation
export async function GET() {
const users = await db.user.findMany();
return Response.json(users);
}
5. Practice Exercises
Exercise 1: Configuring Static Route Handler Caching
Scenario:
Create a static Route Handler app/api/static-data/route.ts that caches JSON responses at build time.
Requirements:
- Export
GEThandler returningResponse.json().
Answer
Implementation
// app/api/static-data/route.ts
export async function GET() {
const data = { version: "1.0.0", buildTime: new Date().toISOString() };
return Response.json(data);
}
Technical Explanation
- In Next.js App Router,
GETRoute Handlers without dynamic parameters are cached statically by default. - Executed once during
next buildand served as static JSON artifacts. - Delivers ultra-fast CDN edge response performance.
Exercise 2: Opting Out of Route Handler Caching with dynamic = 'force-dynamic'
Scenario:
Force a GET Route Handler to bypass static caching and run dynamically on every request.
Requirements:
- Export
export const dynamic = 'force-dynamic'.
Answer
Implementation
// app/api/live-status/route.ts
export const dynamic = "force-dynamic";
export async function GET() {
return Response.json({
status: "online",
timestamp: Date.now()
});
}
Technical Explanation
export const dynamic = 'force-dynamic'instructs Next.js to bypass static route caching.- Re-evaluates the Route Handler logic on Node.js/edge servers for every incoming HTTP request.
- Essential for real-time telemetry or status endpoints.
Exercise 3: Time-Based Route Handler Revalidation (revalidate)
Scenario:
Configure a GET Route Handler to revalidate cached JSON output every 60 seconds.
Requirements:
- Export
export const revalidate = 60.
Answer
Implementation
// app/api/news/route.ts
export const revalidate = 60;
export async function GET() {
const news = await fetch("https://api.example.com/raw-news").then((r) => r.json());
return Response.json(news);
}
Technical Explanation
export const revalidate = secondsenables Stale-While-Revalidate (SWR) for the Route Handler.- Caches JSON responses for 60 seconds before triggering background revalidation.
- Efficient API response caching pattern.
6. Related Terms
- Data Caching (
force-cache,no-store) — Caching individual fetches rather than whole routes. - Dynamic Route Handlers — Routes that are automatically dynamic by default.
- Route Handlers (
route.ts) — Related concept: Route Handlers (route.ts).
7. Key Takeaways
GETRoute Handlers are cached statically by default.POST,PUT, andDELETEhandlers are never cached.- A
GETroute becomes dynamic automatically if it reads theRequestobject, readscookies(), or uses[dynamic]folder names. - You can manually force a route to never cache by exporting
export const dynamic = 'force-dynamic';. - Development mode behaves differently than Production mode regarding caching. Always verify your API caching strategy!