Static output and route cache
Use revalidated SSR, build-time parameter enumeration, vary keys, and cache tags while avoiding unresolved permanent-cache behavior.
Choose one settled cache policy
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 | Use one form only; see the permanent-cache contract note 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 type {
PageLoadContext,
PageProps
} from "@tavojs/core/router";
import { Page, Text } from "@tavojs/ui";
type Product = { id: string; name: string };
type ProductParams = { id: string };
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 async function load({
params,
signal,
url
}: PageLoadContext): Promise<Product> {
const response = await fetch(
new URL(`/api/catalog/${params.id}`, url),
{ signal }
);
if (!response.ok) {
throw new Error("Could not load product");
}
return response.json() as Promise<Product>;
}
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.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.
Keep permanent static caching behind verification
The public intent of both prerender = true and static: true in defineRoutePage is build-time static HTML without revalidation. Current Core also gives a non-revalidated static route a long-lived immutable runtime cache response. That permanent runtime behavior is still under contract review.
Use numeric revalidate when the Node runtime must refresh content; its cache behavior is explicit and settled.
Use prerender or helper static only when build-time output is the intended source and the deployed asset/cache layer has been verified.
Do not document the current immutable Node response as a permanent application guarantee until Core resolves the contract.
A change to this behavior may require a Core migration note even if the authored route export stays the same.
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.