- Next.js
- Next.js Caching Architecture
- Dynamic Rendering vs Static Rendering Triggers
Dynamic Rendering vs Static Rendering Triggers
In Next.js App Router, routes are statically prerendered by default unless they consume dynamic functions (cookies(), headers(), searchParams) or an uncached data source. When a dynamic trigger is accessed, Next.js automatically switches that segment from static build-time generation to request-time dynamic rendering.
Learning Objectives
Identify the exact functions and APIs that trigger dynamic rendering.
Inspect build output indicators (○ Static vs λ Dynamic) to audit route rendering mode.
Use Route Segment Config options (dynamic = "error" / "force-static") to enforce static guarantees.
Isolate dynamic functions to child components to keep parent layouts static.
Prerequisites & Background Knowledge
Why Interviewers Care
Essential for maintaining high CDN cache-hit rates and keeping cloud server compute costs under control.
The Challenge
List all triggers that switch a Next.js route from static to dynamic rendering. How can you programmatically prevent accidental dynamic bailout in production codebases?
Code Architecture & Implementation
1// app/marketing/page.tsx2// 1. Force Next.js to fail the build if any dynamic function is introduced3export const dynamic = 'error';45// If someone tries to use cookies() or headers() here, the build throws:6// "Error: Page couldn't be rendered statically because it used cookies()"78export default async function StaticMarketingPage() {9 const content = await fetch('https://api.cms.com/marketing', {10 next: { revalidate: 86400 } // 24-hour ISR11 }).then(res => res.json());1213 return (14 <main>15 <h1>{content.headline}</h1>16 <p>{content.body}</p>17 </main>18 );19}
Architectural Explanation
The dynamic triggers in Next.js are: 1) cookies(), 2) headers(), 3) searchParams page props, and 4) fetch(..., { cache: "no-store" }) when unconfigured. When Next.js encounters any of these during next build, it aborts static generation for that route and marks it with the λ symbol in the terminal output. By wrapping dynamic components in <Suspense>, developers allow the surrounding shell/layout to be prerendered statically while only streaming the dynamic component at request time.
Common Mistakes & Anti-Patterns
Reading `cookies()` in the root `layout.tsx`, accidentally turning the ENTIRE website dynamic.
Using `searchParams` in a page when an interactive Client Component with `useSearchParams()` inside `<Suspense>` could keep the page shell static.
Not reviewing `npm run build` output symbols (○ vs λ) before deploying to production.
Interview Follow-Up Traps
The interviewer asks: "Does calling headers() inside a generateMetadata function make the page dynamic?" Yes! `headers()` inside `generateMetadata()` triggers dynamic rendering for the entire route.
Frequently Asked Questions
Can a route with dynamic = "force-static" use cookies()?
No. `cookies()` will return empty objects or throw an error.
How does Partial Prerendering (PPR) change this dynamic trigger model?
Under PPR, routes are no longer strictly all-or-nothing; static shells are always prerendered, and dynamic triggers are isolated to streamed Suspense holes.
