Static & Dynamic Rendering Strategies
Dinou is Static by Default. It provides a continuum of rendering strategies—from build-time SSG and stale-while-revalidate ISR to real-time Dynamic SSR, revolutionary Partial Prerendering (PPR), and component-level Micro-ISR with <DinouCacheSlot>.
Overview: Static by Default Architecture
In Dinou, every route is assumed to be static until proven otherwise. During dinou build, the compiler analyzes your route segments, components, and loader functions to evaluate whether a route can be pre-rendered or if it requires real-time request data:
revalidate: 0, Dinou automatically bails out to Live SSR on every request.<DinouCacheSlot>.1. Static Site Generation (SSG) & Bailout
Pure static rendering pre-generates HTML files during the build phase (dinou build). Production server startup is instantaneous, and static pages achieve the fastest possible Time to First Byte (TTFB) and can be cached by edge CDNs globally.
1.1. Pure Static Pre-rendering
Any page that does not consume request-specific headers, cookies, or query parameters (context.req.query) and does not set revalidate < 1 is automatically generated as static HTML:
// src/about/page.tsx
// Automatically rendered as static HTML during build time
export default function AboutPage() {
return (
<article className="prose">
<h1>About Us</h1>
<p>This page is pre-rendered once and served statically forever.</p>
</article>
);
}1.2. Automatic Static Bailout
If a Server Component reads request-specific data at render time without wrapping it inside a dynamic streaming boundary (React <Suspense> with Partial Prerendering / PPR enabled), Dinou automatically "bails out" of static generation and transitions the entire page into Dynamic SSR for that request:
// src/profile/page.tsx
import { getContext } from "dinou";
import db from "@/lib/db";
export default async function ProfilePage() {
const context = getContext();
// ⚠️ Reading cookies outside <Suspense> triggers an Automatic Static Bailout
// This page will now be rendered dynamically on every incoming request
const sessionToken = context?.req.cookies["session_token"];
const user = await db.users.findByToken(sessionToken);
return <h1>Welcome back, {user?.name}</h1>;
}Bailout Triggers in Server Components
context.req.cookies, context.req.headers, or context.req.query (URL search parameters) during server component execution without wrapping the access in a React <Suspense> boundary.useSearchParams()?Client Components ("use client") that call useSearchParams() do not trigger dynamic bailout. The page remains 100% static (SSG). During build time, the component is pre-rendered with an empty parameter set, and live URL parameters are synchronized in the browser upon hydration.
⚠️Understanding and Preventing Hydration Mismatches
If a Client Component immediately renders searchParams.get("q") in its initial JSX markup, the server-generated static HTML (where the query was empty at build time) will differ from the live browser DOM (e.g. ?q=react), triggering a React hydration mismatch error.
Recommended Fix: Deferred Synchronization with useEffect
Keep the initial render deterministic (matching the static build HTML), then read and apply the live search parameters inside useEffect() after the component mounts in the browser:
// src/components/search-filter.tsx
"use client";
import { useSearchParams } from "dinou";
import { useState, useEffect } from "react";
export function SearchFilter() {
const searchParams = useSearchParams();
const [query, setQuery] = useState<string | null>(null);
useEffect(() => {
// Synchronize client-side only after mounting -> eliminates hydration mismatch
setQuery(searchParams.get("q") ?? "");
}, [searchParams]);
// Initial render matches static HTML perfectly
if (query === null) {
return <span className="text-muted-foreground">Loading filter...</span>;
}
return <span>Filter: <strong>{query}</strong></span>;
}export const revalidate: PageFunctions["revalidate"] = 0; in page_functions.ts, or read context.req.query inside a dynamic Server Component.2. The Unified revalidate Strategy (Seconds)
In Dinou v7, the revalidate export in page_functions.ts (or layout_functions.ts) serves as the single source of truth for the route segment's rendering mode. It is measured strictly in seconds (s).
2.1. Dynamic SSR (revalidate < 1 or 0)
To force a page to render dynamically on every single request (escaping the default static generation), set revalidate to 0 (or any number < 1):
// src/analytics/live/page_functions.ts
// Escapes static generation: executes dynamic SSR on every request
export const revalidate = 0;Migration from Legacy dynamic Export
export function dynamic(). In Dinou v7, the dynamic export has been removed. Setting export const revalidate = 0; is the official unified mechanism to escape static generation.2.2. Incremental Static Regeneration (revalidate >= 1)
When revalidate >= 1, Dinou serves pre-rendered static HTML/RSC payloads while regenerating the page in the background at most once every N seconds (stale-while-revalidate):
// src/blog/page_functions.ts
// Rebuilds the cached page in the background at most once every 60 seconds
export const revalidate = 60;
// Or as a sync/async function:
// export function revalidate() { return 60; }60 for 1 minute, 3600 for 1 hour). If false or omitted, the page remains static forever until purged.3. Partial Prerendering (PPR)
Breakthrough in v7Partial Prerendering (PPR) resolves the decades-old dilemma between static and dynamic rendering. Instead of forcing an entire page to be 100% static or 100% dynamic, PPR delivers a pre-rendered static shell instantly, while streaming dynamic holes in parallel over the same HTTP response.
3.1. Hybrid Architecture: Static Shell + Dynamic Holes
At build time, Dinou compiles the layout, navigation, and static components into an immutable HTML shell. Any dynamic component wrapped in React <Suspense> is saved as a streaming placeholder (hole).
- 0ms (Instant TTFB): The browser receives the static shell with skeleton fallbacks. Visual layout is rendered immediately.
- Request Processing: Dinou executes async Server Components inside
<Suspense>on the server. - Streaming In-Place: As each Suspense promise resolves, the HTML chunk and RSC payload are streamed down the same HTTP connection, replacing the skeleton with real content without full page reloads.
Data Freshness: Pure SSG vs. Partial Prerendering
In standard Static Site Generation (without PPR), all server components and database queries execute exclusively at build time. The resulting HTML is immutable:
Data is baked into the build artifact. Live inventory, user carts, or real-time counters are stale upon deployment. Historically, applications resorted to legacy client-side useEffect() + fetch() anti-patterns, suffering network waterfalls, layout shifts, and flickering spinners.
The visual shell is delivered at 0ms TTFB from the edge, while the component inside <Suspense> executes on the server on every single incoming HTTP request. You get 100% fresh, live database data streamed directly to the browser without client waterfalls or legacy useEffect fetching.
PPR as a Bailout Boundary: Containing Dynamic De-optimization
Without PPR, dynamic data access is infectious. If a single nested component reads request cookies or headers, Dinou triggers an Automatic Static Bailout for the entire page, destroying static pre-rendering and edge caching for the whole route.
With PPR enabled (ppr: true), React <Suspense> acts as an isolated sandbox. Dynamic data consumption inside the boundary does not escape to parent or sibling components:
// src/store/page.tsx
import { Suspense } from "react";
import { getContext } from "dinou";
import db from "@/lib/db";
import { ProductGallery } from "@/components/gallery";
import { CartBadgeSkeleton } from "@/components/skeletons";
// ⚠️ Dynamic component: reads request-specific session cookie
async function LiveCartBadge() {
const context = getContext();
const sessionToken = context?.req.cookies["session_id"];
const cart = await db.carts.findActive(sessionToken);
return <div className="cart-badge">Items: {cart?.itemCount ?? 0}</div>;
}
export default function StorePage() {
return (
<main className="container">
{/* 1. Static Shell: 0ms TTFB, pre-rendered during build */}
<header className="navbar">
<h1>Flagship Store</h1>
{/* 2. PPR Boundary: Isolates cookie bailout exclusively to this hole */}
<Suspense fallback={<CartBadgeSkeleton />}>
<LiveCartBadge />
</Suspense>
</header>
{/* 3. Static Sibling: Remains 100% static and edge-cached */}
<ProductGallery />
</main>
);
}Reading cookies["session_id"] triggers an uncontained bailout. The entire page (navbar, product gallery, layout) turns into dynamic SSR. The edge cache is bypassed, and the browser receives 0 bytes until the database query completes.
The cookie bailout is quarantined inside <Suspense>. The navbar and product gallery are delivered instantly at 0ms TTFB. Only the cart badge executes on the server and streams into place.
3.2. Enabling PPR & Specificity Hierarchy
Dinou provides granular control over Partial Prerendering across three hierarchical levels: Global (application-wide), Layout (segment subtree), and Page (individual route).
Configuration cascades strictly from general to specific. A more specific level always overrides a less specific one:
1. Global Configuration (dinou.config.js / .mjs)
When enabled globally, all static-eligible routes in your application will automatically generate static shells and stream dynamic Suspense holes unless explicitly disabled at the layout or page level:
// dinou.config.mjs
import { defineConfig } from "dinou";
export default defineConfig({
// Enables Partial Prerendering globally for the entire app
ppr: true,
});2. Layout Level (layout_functions.ts) — Subtree Cascading
A setting in layout_functions.ts applies to that layout and cascades to all its descendant subroutes. For example, if PPR is enabled globally, you can opt out an entire authenticated admin section:
// src/admin/layout_functions.ts
// Overrides global ppr: true.
// Disables PPR for /admin and ALL subroutes (/admin/users, /admin/settings, etc.)
export const ppr = false;3. Page Level (page_functions.ts) — Maximum Specificity
A page can override both its parent layout and the global configuration. This allows enabling PPR on a specific high-traffic marketing page or turning it off on an individual volatile checkout page:
// src/shop/products/[slug]/page_functions.ts
// Overrides any parent setting to guarantee PPR on product details
export const ppr = true;Component Implementation Pattern
Once PPR is active, wrap dynamic data fetching components in React <Suspense> to create the streaming holes:
// src/shop/products/[slug]/page.tsx
import { Suspense } from "react";
import { ProductGallery, ProductDetails } from "./static-components";
import { RealtimeStock, StockSkeleton } from "./dynamic-components";
export default function ProductPage({ params }: { params: { slug: string } }) {
return (
<div className="product-layout">
{/* 1. Static Shell: pre-rendered at build time and served in 0ms */}
<ProductGallery slug={params.slug} />
<ProductDetails slug={params.slug} />
{/* 2. Dynamic Hole: streamed in real-time over the same HTTP response */}
<Suspense fallback={<StockSkeleton />}>
<RealtimeStock slug={params.slug} />
</Suspense>
</div>
);
}4. Vertical Segmentation: Micro-ISR (<DinouCacheSlot>)
Exclusive to DinouTraditional frameworks only offer Horizontal Segmentation (caching an entire URL route or layout shell). When a dynamic page contains an expensive database widget, developers are often forced into an architectural trap: converting the widget to a Client Component with client-side cache libraries (SWR, TanStack Query), introducing bundle bloat, waterfall requests, and layout shifts.
Dinou v7 introduces Vertical Segmentation with <DinouCacheSlot> from "dinou/server". It keeps components securely on the server as React Server Components (RSC) while caching their rendered serialized Flight payload in server storage with dedicated TTLs and invalidation tags.
4.1. Component Caching: Dynamic vs. Static Patterns
A frequent question is: Is <DinouCacheSlot> meant for dynamic or static pages? The answer is that it functions as a universal caching primitive across three core patterns:
The outer route is dynamic (reading session cookies, auth tokens, or revalidate = 0). Without a slot, every user request re-executes all expensive queries. With <DinouCacheSlot>, the personalized user greeting renders live, while the 2-second analytics calculation is served from server cache in ~2ms.
A product catalog page has a slow 24-hour page revalidation (revalidate: 86400). Inside it, a real-time inventory badge or flash sale counter uses <DinouCacheSlot revalidate=60>. The heavy layout remains cached at the edge, while the volatile widget updates independently.
Implementation Pattern 1: Dynamic SSR Dashboard with Cached Slot
The route runs in Live SSR mode (evaluating user cookies and personal widgets live). The heavy analytics calculation is cached on the server for 300 seconds and wrapped in React <Suspense> to prevent cold-start cache misses from delaying TTFB:
// src/dashboard/page.tsx
import { Suspense } from "react";
import { DinouCacheSlot } from "dinou/server";
import { FastUserProfile, SlowAnalyticsWidget } from "./widgets";
import { AnalyticsSkeleton } from "./skeletons";
export default function DashboardPage() {
return (
<main className="grid grid-cols-1 md:grid-cols-2 gap-6 p-6">
{/* 1. Dynamic Component: Evaluated live on every request (reads session/cookies) */}
<FastUserProfile />
{/* 2. Micro-ISR Slot: Cached on the server for 300 seconds */}
{/* ⚠️ Wrapped in <Suspense> so cache misses stream without blocking TTFB */}
<Suspense fallback={<AnalyticsSkeleton />}>
<DinouCacheSlot id="analytics-widget" tag="analytics" revalidate={300}>
<SlowAnalyticsWidget />
</DinouCacheSlot>
</Suspense>
</main>
);
}Implementation Pattern 2: Static / ISR Product Page with Asymmetric Slot
The parent route has a slow horizontal revalidation of 24 hours (revalidate: 86400). Inside it, a volatile inventory counter revalidates vertically every 60 seconds (revalidate=60) or on-demand when purchases occur:
// src/products/[slug]/page_functions.ts
import type { PageFunctions, RouteParams } from "dinou";
export type ProductParams = RouteParams<"/products/[slug]">;
// 1. Slow horizontal revalidation for outer layout & descriptions (24 hours = 86,400s)
export const revalidate: PageFunctions["revalidate"] = 86400;
// src/products/[slug]/page.tsx
import { Suspense } from "react";
import { DinouCacheSlot } from "dinou/server";
import type { PageProps } from "dinou";
import { ProductHero, ProductDescription } from "./static-widgets";
import { LiveStockCounter, StockSkeleton } from "./stock-widgets";
export default function ProductDetailPage({ params }: PageProps<"/products/[slug]">) {
return (
<main className="container py-8 space-y-6">
{/* 1. Heavy Static Content: Cached at the CDN edge for 24 hours */}
<ProductHero slug={params.slug} />
<ProductDescription slug={params.slug} />
{/* 2. Asymmetric Micro-ISR Slot: Revalidates vertically every 60s */}
{/* 💡 In pure SSG/ISR, <Suspense> is optional at build time, but recommended for ISG on-demand hits & refreshSlot() */}
<Suspense fallback={<StockSkeleton />}>
<DinouCacheSlot
id={`stock-${params.slug}`}
tag={`stock-${params.slug}`}
revalidate={60}
>
<LiveStockCounter slug={params.slug} />
</DinouCacheSlot>
</Suspense>
</main>
);
}Is <Suspense> mandatory in Pattern 2? In pure SSG/ISR, wrapping the slot in <Suspense> is optional because the page is pre-rendered at build time (or during background revalidation). However, wrapping it in <Suspense> is considered a best practice because: (1) if an uncached path is generated on demand via ISG, it prevents slow database queries from blocking TTFB for the first visitor; (2) it provides a loading state during client-side refreshSlot() transitions; and (3) it ensures seamless compatibility if the route enables PPR.
<Suspense> Must Wrap <DinouCacheSlot>A common architectural question is: Can <Suspense> live inside the child component (e.g. inside <SlowAnalyticsWidget>) instead of wrapping <DinouCacheSlot>?
<DinouCacheSlot> is itself an async Server Component (async function DinouCacheSlot). When it awaits storage or renders on a cache miss, React encounters a pending promise in the parent page tree. Without an outer <Suspense>, suspension bubbles to the root, blocking the entire HTTP response and delaying fast sibling components like <FastUserProfile>.
The outer <Suspense> captures the async execution of <DinouCacheSlot>. The shell and user profile flush to the browser in 0ms TTFB with the skeleton fallback. On a cache hit (~2ms), the slot resolves immediately; on a cache miss, it computes in the background and streams into place.
The Rule of Hierarchy:
4.2. Interactive Revalidation & On-Demand Purging
Dinou provides two specialized mechanisms to refresh or purge <DinouCacheSlot> instances without re-rendering the surrounding page:
refreshSlot)Any interactive Client Component can trigger a surgical re-render of only that slot without a full page reload or state loss. You can invoke either the standalone refreshSlot() function or router.refreshSlot() from useRouter():
"use client";
import { refreshSlot, useRouter } from "dinou";
export function RefreshButton() {
const router = useRouter();
return (
<div className="flex gap-2">
{/* Option 1: Standalone (respects server slot TTL / tags) */}
<button onClick={() => refreshSlot("analytics-widget")}>
Refresh Metrics
</button>
{/* Option 2: Force fresh (bypasses server slot TTL immediately) */}
<button onClick={() => router.refreshSlot("analytics-widget", { fresh: true })}>
Force Fresh
</button>
</div>
);
}fresh: true exist if it is already called refreshSlot?refreshSlot(id) (Default): Refreshes the client DOM with the server's current state. If the slot's server-side TTL (e.g. revalidate=300) has not expired, it returns the cached server JSX without touching your database (protecting against button spam and traffic spikes).refreshSlot(id, { fresh: true }) (Bypass): Hard revalidation. Forces the server to ignore any unexpired TTL, immediately re-run the server component's queries/computations against your database, and update both the cache and client.revalidateTag)Mutations purge slots globally via revalidateTag() on the server, while the client immediately pulls the fresh result via refreshSlot():
// src/app/feed/actions.ts
"use server";
import { revalidateTag } from "dinou";
export async function publishPost(formData: FormData) {
const content = formData.get("content") as string;
await db.posts.insert({ content, createdAt: new Date() });
// 1. Purges cache globally for ALL future visitors
await revalidateTag("feed");
}revalidateTag + refreshSlot:revalidateTag("feed"): Evicts and purges the server cache globally for all future users.refreshSlot("feed-widget"): Pulls the fresh JSX immediately into the current user's screen without needing { fresh: true } (since the cache is already purged).refreshSlot() vs. Route-Level SWRA key architectural question when adopting Micro-ISR is: When a slot's TTL expires, does refreshSlot() serve stale cache first and revalidate in the background (like traditional SWR)? And does the user see a jarring skeleton flash?
During initial SSR or SPA page navigation, <DinouCacheSlot> prioritizes instant TTFB. If a slot's TTL has expired, the server serves the stale cache in 0ms and triggers an asynchronous background revalidation so subsequent visitors receive fresh content.
When a user explicitly clicks a refresh button, SWR would deliver a confusing UX (showing old data again). Therefore, the granular endpoint (/____rsc_slot____/:id) awaits the server component execution, writes the updated result to cache, and returns the fresh RSC payload in that single HTTP response.
While the server re-renders the expired slot, the user does NOT see a blank skeleton flash or layout shift. Dinou's internal client boundary (DinouCacheSlotBoundary) wraps slot updates in React.startTransition and sets fallback: children (the previous DOM content). The existing widget stays interactive and visible until the new server stream arrives, then swaps seamlessly.
Client-Side Pending State Pattern:
If you want your button or UI to show an inline spinner or disabled state while refreshSlot() resolves the fresh server data, wrap the call in React's standard useTransition() hook:
"use client";
import { useTransition } from "react";
import { refreshSlot } from "dinou";
export function RefreshAnalyticsButton() {
const [isPending, startTransition] = useTransition();
const handleRefresh = () => {
startTransition(() => {
// Server awaits execution on expired TTL; UI keeps current widget visible
refreshSlot("analytics-widget");
});
};
return (
<button onClick={handleRefresh} disabled={isPending} className="flex items-center gap-2">
{isPending ? "Refreshing Metrics..." : "Refresh Metrics"}
</button>
);
}4.3. Horizontal vs. Vertical Segmentation & Anti-Patterns
Configured in page_functions.ts and layout_functions.ts. Governs route-level SSG, ISR, and PPR.
Configured via <DinouCacheSlot>. Isolates component subtrees with independent revalidation and on-demand tag purging.
If an entire route is pre-rendered at build time with no revalidation, it is already cached as immutable HTML at the CDN. Slots add zero benefit.
Wrapping the entire page.tsx body inside a slot reinvents horizontal caching. Declare export const revalidate = 300 in page_functions.ts instead.
Never wrap private user data (such as a specific user's cart or auth session) with a static slot ID. This would cache user A's private data and leak it to user B.
5. High-Traffic Synergy: PPR + <DinouCacheSlot>
Maximum ConcurrencyWhat happens when you nest <DinouCacheSlot> inside a React <Suspense> boundary on a route with Partial Prerendering (PPR)? You unlock the highest performance profile possible in full-stack web engineering: zero-delay initial delivery (0ms TTFB) paired with near-zero streaming delay (~2ms) and 99.9% database query offloading.
- TTFB: 0ms from CDN Edge (instant static shell).
- Hole Resolution: The user waits the full database query time (e.g. 1,500ms) on every single request while the server executes the query.
- Database Pressure: 10,000 visitors/min = 10,000 live database queries.
- TTFB: 0ms from CDN Edge (instant static shell).
- Hole Resolution: Resolves from server cache in ~2ms (the user barely perceives the skeleton fallback).
- Database Pressure: 10,000 visitors/min = 1 database query per 300s TTL (99.9% database offload).
Implementation Pattern: High-Traffic Telemetry Dashboard
The page shell is pre-rendered at build time with PPR enabled (ppr: true). The dynamic metrics calculation is wrapped in <DinouCacheSlot> inside React <Suspense>:
// src/telemetry/page_functions.ts
import type { PageFunctions } from "dinou";
// Enable Partial Prerendering on this high-traffic route
export const ppr: PageFunctions["ppr"] = true;
// src/telemetry/page.tsx
import { Suspense } from "react";
import { DinouCacheSlot } from "dinou/server";
import { TelemetryHeader, TelemetryFooter } from "./static-shell";
import { HeavyMetricsGraph, GraphSkeleton } from "./graph-widgets";
export default function TelemetryPage() {
return (
<main className="container py-8 space-y-6">
{/* 1. Static Shell: Delivered instantly in 0ms TTFB from edge CDN */}
<TelemetryHeader />
{/* 2. PPR Dynamic Hole + Micro-ISR Slot:
- Fallback skeleton sent immediately in initial layout
- Hole streams from server cache in ~2ms for subsequent visitors
- Database query runs only ONCE every 60 seconds */}
<Suspense fallback={<GraphSkeleton />}>
<DinouCacheSlot id="operations-telemetry" tag="telemetry" revalidate={60}>
<HeavyMetricsGraph />
</DinouCacheSlot>
</Suspense>
<TelemetryFooter />
</main>
);
}Outer shell and fallback skeleton arrive at 0ms from the CDN edge cache.
Subsequent visitors receive the precalculated RSC Flight stream almost instantly.
High concurrency surges no longer overwhelm primary databases with duplicate queries.
6. Incremental Static Generation (ISG on-demand)
While ISR updates existing pre-rendered pages, ISG (Incremental Static Generation) generates new pages on demand that were not included in the initial build-time getStaticPaths list.
6.1. On-Demand Path Generation
Any route path omitted from getStaticPaths will be generated dynamically when the first visitor requests it, cached, and then served as static HTML to subsequent visitors:
// src/products/[id]/page_functions.ts
// Pre-render only the top 10 products at build time
export const getStaticPaths = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"];
// Any other ID (e.g. /products/452) will be generated on-demand
// on its first visitor and cached permanently.6.2. Governance APIs (validateParams & allowISG)
Dinou provides strict server-side governance exports to secure dynamic generation against abuse:
Validates parameters before generation. Returning false halts rendering and returns 404 immediately.
export function validateParams(params) {
return /^\d+$/.test(params.id); // Only allow numeric IDs
}Disables on-demand generation. If set to false, any path not in getStaticPaths returns 404 instantly.
export const allowISG = false; // Strict SSG only7. Complete Rendering Strategies Matrix
Summary of all rendering strategies supported natively by Dinou v7:
| Strategy | How to Configure | TTFB | Freshness | Database Load | Best For |
|---|---|---|---|---|---|
| Pure SSG | Default / revalidate = false | 0ms (Instant) | Build-time | 0 queries (Build-time only) | Landing pages, marketing, documentation |
| ISR | revalidate = N (N >= 1) | 0ms (Cached) | Stale-while-revalidate | 1 query per TTL (0 on cache hits) | Blogs, catalogs, news feeds |
| Dynamic SSR | revalidate = 0 | Request-time (Blocked) | 100% Real-time | Heavy: 100% queries on every request | User settings, banking, checkout |
| PPR (Partial Prerendering) | ppr = true + <Suspense> | 0ms (Shell from Edge) | 100% Real-time (Holes) | Heavy: 100% Hole queries on every request | E-commerce, dashboards with static shells |
| Micro-ISR (Static / Asymmetric ISR) | <DinouCacheSlot> (in Static/ISR page) | 0ms (CDN Edge) | Decoupled Slot TTL / Tags | Low: Isolated to expired slots | Static product/catalog pages with volatile widgets (stock, prices) |
| Micro-ISR (Dynamic SSR) | <DinouCacheSlot> (in Dynamic SSR page) | Fast (Early Layout / Slot-cached) | Component TTL / Tags | Low: Isolated to expired slots | Dynamic user dashboards with heavy SQL calculations |
| PPR + Micro-ISR (Synergy) | ppr: true + <DinouCacheSlot> | 0ms (Edge) + ~2ms (Hole) | Cached Streamed Hole | Near-Zero: 1 query per slot TTL (99.9% reduction) | High-concurrency e-commerce, telemetry dashboards |
| ISG (Incremental Static) | allowISG = true (default) | 0ms (after 1st hit) | Cached permanently | 1 query on 1st generation (0 thereafter) | Long-tail e-commerce products, dynamic paths |