Static output and route cache
Choose build-time output or revalidated SSR, enumerate dynamic paths, and control shared response caching with vary keys and cache tags.
Choose caching for the deployment you run
tsxexport const revalidate = 300;
export const cacheTags = ["catalog"];
export default function CatalogPage() {
return <main>Catalog</main>;
}| Intent | Functional module | defineRoutePage | Behavior |
|---|---|---|---|
| Cached SSR with regeneration | export const revalidate = 300 | revalidate: 300 | Numeric revalidate automatically enables static caching. |
| Build-time static HTML without revalidation | export const prerender = true | static: true | On Node, also enables a long-lived immutable response; review its freshness implications below. |
| Disable inherited static policy | export const revalidate = false | revalidate: false | Resets inherited static and revalidation policy. |
Declare revalidated dynamic output
For a dynamic cached route, generateStaticParams() returns PageStaticParams for the paths the build should materialize. The loader and cache-tag resolver receive PageLoadContext.
tsximport { notFound, type PageLoadContext, type PageProps } from "@tavojs/core/router";
import { Page, Text } from "@tavojs/ui";
type Product = { id: string; name: string };
type ProductParams = { id: string };
const catalog: Record<string, Product> = {
starter: { id: "starter", name: "Starter plan" },
team: { id: "team", name: "Team plan" }
};
export const revalidate = 300;
export const vary = "accept-language";
export function cacheTags({
params
}: PageLoadContext): string[] {
return ["catalog", `product:${params.id}`];
}
export function generateStaticParams(): ProductParams[] {
return [{ id: "starter" }, { id: "team" }];
}
export function load({ params }: PageLoadContext): Product {
return catalog[params.id] ?? notFound();
}
export default function ProductPage({
data
}: PageProps<Product, ProductParams>) {
return (
<Page>
<Text as="h1" variant="h1">
{data?.name}
</Text>
</Page>
);
}generateStaticParamsreturns parameter records for dynamic paths the build must enumerate. Every record must provide values expected by that route pattern.This catalog is local so the example can prerender without another server. A production build needs access to every loader data source; an application-relative fetch does not start your own /api endpoint during the build.
A numeric revalidate is measured in seconds, rounded down, and clamped to zero.
Static policy composes through the route chain. The shortest finite revalidate value wins.
vary names are lowercased and deduplicated. Localization also adds Accept-Language variation.
CSR routes ignore static policy and static params with a manifest diagnostic.
Understand Node cache behavior
Inspection exposes the composed PageCachePolicy. Each static or request-aware tag declaration follows PageCacheTags.
A successful route with numeric revalidate emits Cache-Control: public, max-age=0, s-maxage=N.
Tavo.jsuses the static cache only for responses eligible under the composed route policy.Non-200 responses, resolved route errors, and requests with personal headers do not receive shared static cache headers.
Vary values must cover every request header that can change shared output. Missing variation can serve one user's representation to another.
Cache tags support targeted invalidation where the selected runtime exposes it. Attach tags to the page or documented directory layouts.
Understand static output without a revalidation interval
Both prerender = true and static: true in defineRoutePage enable static output without a refresh interval. In Core 1.0.4, eligible Node responses also use an unexpired in-process cache entry and send Cache-Control: public, max-age=31536000, immutable. Treat this as content that can remain cached for a year downstream.
Use numeric revalidate when the Node runtime must refresh changing content. A static host cannot execute revalidation; rebuild and redeploy its generated HTML.
Use prerender or helper static only when build-time output is the intended source and the deployed asset/cache layer has been verified.
Server cache invalidation does not clear a browser or CDN cache that has already stored an immutable response. Plan a host purge or a versioned URL when publishing changed content.
Restarting a Node process clears its default memory cache. A custom persistent cache needs its own deployment and invalidation policy.
Cache only shareable successful output
Do not statically cache a route whose output depends on a session, authorization decision, private cookie, or user-specific request header.
A cache tag identifies related entries; it does not make private output safe to share.
Return the correct non-200 status for failures and not-found output so they are not mistaken for a successful reusable document.
Keep cache keys and tags bounded. Never place secret values in either one because operational tooling may expose them.
When variation or privacy is uncertain, prefer uncached SSR and add caching only after request-level tests prove isolation.
Inspect generation and response headers
bashnpx tavo inspect route /catalog/starter --json
npx tavo build
PORT=4174 node .tavo/build/server/start.mjs
curl -i http://127.0.0.1:4174/catalog/starterConfirm the inspected route has the expected static, revalidate, vary, and tag policy.
Confirm every generated dynamic parameter produces output at the intended path.
Assert Cache-Control and Vary for a successful anonymous request.
Repeat with the deployment's personal headers and failure cases and confirm shared cache headers are absent.
Exercise tag invalidation only through a runtime that explicitly exposes and documents it.
Look up exact public types
Follow linked API names to their canonical TypeScript declarations and package boundaries.