Incremental Static Regeneration
ISR
Static pages that stay fresh: prerender once, serve from cache, and revalidate on a timer. ISR gives you the speed of a static export with data that eventually updates.
Prerender with revalidation
A page that exports mode = "static" plus revalidate is prerendered at build time and then re-rendered on demand once the cache expires.
import type { RouteProps, LoaderArgs } from "@thexjs/core";export const mode = "static";export const revalidate = 3600; // secondsexport async function loader({}: LoaderArgs) { const stats = await fetchStats(); return { stats };}export default function Stats({ loaderData }: RouteProps) { const { stats } = loaderData as { stats: unknown }; return ( <div> <h1 className="text-3xl font-bold">Stats</h1> <pre>{JSON.stringify(stats, null, 2)}</pre> </div> );}revalidate accepts dynamic pages too. A page with mode = "server" and revalidate behaves like the classic ISR model: the first request after a cache miss renders fresh, subsequent requests within the window are served from cache.
Cache headers
Responses from a revalidated page carry an X-Revalidated header so you can tell how the page was served:
X-Revalidated: hit # served from the static cacheX-Revalidated: miss # rendered fresh, cache was stale or emptyX-Revalidated: none # page has no revalidate windowInvalidating on demand
You don't have to wait for the timer. A POST /__x/revalidate request with a JSON body { "path": "/stats" } clears that page's cache entry, so the next request renders fresh. An empty body clears the entire cache.
await fetch("http://localhost:3000/__x/revalidate", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ path: "/stats" }),});This is the hook for a CMS webhook or an admin action: a content edit can push a fresh render immediately instead of waiting for the revalidate window.