SSR and hydration
Choose SSR, CSR, SSG, or revalidated output and keep the initial client tree consistent with server HTML.
Choose rendering per route
The same page and component code can render on the server or client. SSR is the default in SSR development, preview, and the generated Node server. Use CSR only when the route depends on browser-only behavior and its initial HTML is not important.
SSR renders for each request.
CSR sends the document shell and resolves the route in the browser.
SSG prerenders static routes during build.
ISR caches SSR output and refreshes it after a revalidation interval.
Configure route output
Static routes may provide generateStaticParams for dynamic paths. Revalidated output uses a runtime process-local cache by default, while Cookie or Authorization requests bypass static caching.
tsximport { notFound, type PageLoadContext, type PageProps } from "@tavojs/core/router";
import { Page, Text } from "@tavojs/ui";
const posts: Record<string, { title: string }> = {
hello: { title: "Hello from Tavo.js" },
"release-notes": { title: "Release notes" }
};
export const prerender = true;
export const generateStaticParams = () => [{ id: "hello" }, { id: "release-notes" }];
export const load = ({ params }: PageLoadContext) => posts[params.id] ?? notFound();
export default function BlogPost({ data }: PageProps<{ title: string }>) {
return <Page><Text as="h1" variant="h1">{data?.title}</Text></Page>;
}Keep hydration deterministic
Tavo.js serializes resolved route data so the browser can hydrate against the same route tree without immediately loading it again. Hydration warnings mean server and client produced different initial output.
Do not read browser-only globals during the initial render without a guard.
Use deterministic IDs from controller helpers.
Avoid time, randomness, and locale differences between server and client output.
Test the production SSR build, not only development mode.
Rendering mode reference
Understand exactly how route and layout exports select SSR, CSR, SSG, and revalidated output.
Every route resolves to either SSR or CSR. SSR is the default. Static generation and revalidation are cache policies applied to SSR routes; they are not separate component runtimes.
tsx// Browser-rendered route with a useful server shell.
export const render = "csr";
export default function AccountPage() {
return <main>Account settings</main>;
}| API / option | Type | Default | Behavior |
|---|---|---|---|
render | "csr" | SSR | Export render = "csr" to opt the route subtree out of server body rendering. |
prerender | boolean | false | Marks a functional SSR route for build-time prerendering or runtime static caching. false disables an inherited static policy. |
revalidate | number | false | unset | Enables static SSR caching for the given number of seconds. Values are floored and clamped to zero; false disables inherited caching. |
generateStaticParams | () => params[] | Promise<params[]> | unset | Lists build-time paths for a dynamic static route. |
csrFallback | Child | (context) => Child | empty route node | Provides meaningful server-shell content for a CSR route without executing its client loader. |
Layout and page cache settings compose from root to leaf. Vary headers are trimmed, lowercased, and merged. Static cache tags are merged. If several layers provide numeric revalidation intervals, the shortest interval wins.
Boot and hydration reference
Use bootTavo and getTavoBootMode without guessing how the current document starts.
detects server, hydrated SSR, and browser-only documents. It returns a discriminated result so application entrypoints can inspect what actually started.
| API / option | Type | Default | Behavior |
|---|---|---|---|
root | Element | null | unset | Uses a specific client mount element. |
rootSelector | string | "#app" | Locates the client root when root is not provided. |
hydrate | boolean | detected | Defaults to true when __TAVO_SSR__ or __TAVO_STATE__ exists. A root marked data-tavo-render-mode="csr" always renders instead. |
serverFile | string | "server.mjs" | Server boot returns none when this file is absent. |
modules / node.modules | PageModules | required on server | Supplies the server route module map. Missing modules throw TAVO_PAGES_005. |
getTavoBootMode | () => "server" | "ssr" | "csr" | "none" | — | Reports the planned mode without starting the app. |
tsimport { bootTavo, getTavoBootMode } from "@tavojs/core";
console.log(getTavoBootMode());
const result = await bootTavo();
if (result.mode === "client") {
// result.root exposes render, hydrate, and unmount.
}A missing client root throws
TAVO_PAGES_002and names the expected selector.Hydration restores serialized page, layout, and store state instead of rerunning the initial loaders.
CSR boot resolves the initial loaders and same-origin redirects before mounting the route.
Initial redirect resolution stops after eight redirects and warns about a likely middleware loop.
Server rendering methods
Choose between document helpers, resolved page responses, and production request handlers.
tsximport {
renderDocument,
renderDocumentStream,
} from "@tavojs/core/server";
const html = renderDocument(<App />, {
title: "Dashboard",
initialState: { locale: "en" },
});
const stream = renderDocumentStream(<App />, {
title: "Dashboard",
});| API / option | Type | Default | Behavior |
|---|---|---|---|
renderDocument(node, options) | string | — | Renders the complete HTML document and serialized initial state. |
renderDocumentStream(node, options) | ReadableStream<Uint8Array> | — | Streams the shell followed by deferred patch chunks. |
createPagesRuntimeAsync(modules, options) | Promise<PagesRuntime> | — | Creates the technical server runtime used by generated CLI SSR templates. |
renderPagesResponseFromRuntimeAsync(runtime, pathname, options) | Promise<RenderPagesResponse> | — | Renders a response from that server runtime. Import it only from @tavojs/core/server. |
Static cache and invalidation contract
Understand what is cached, how keys are isolated, and how invalidation reaches both cache layers.
Tavo.js keeps a resolved route-data cache and a rendered-response cache. Both default to 1,024 process-local entries and evict the oldest entry when full. Set maxResolvedCacheEntries to 0 to disable route-data reuse, or provide a custom staticCache for shared rendered output.
Cache keys include request origin, pathname, query string, and declared vary headers.
Localized routes also vary by Accept-Language automatically.
Requests with Cookie or Authorization bypass shared static response caching and do not evict an existing public entry.
Redirects and responses with status 500 or greater are not stored.
Concurrent public renders for the same cache key share one in-flight render.
Cache adapter read, write, and delete failures degrade to an uncached response instead of failing SSR.
tsconst handler = createNodeRequestHandler({ modules, staticCache });
await handler.invalidateCache("post:hello");
await handler.invalidateCache(["posts", "homepage"]);
await handler.clearCache();For process-local caching, use . A custom adapter has the following complete contract, including optional tag invalidation and clearing:
tsimport type {
SsrStaticCache,
SsrStaticCacheEntry,
} from "@tavojs/core/server";
export function createInspectableStaticCache(): SsrStaticCache {
const entries = new Map<string, SsrStaticCacheEntry>();
return {
get(key) {
return entries.get(key) ?? null;
},
set(key, entry) {
entries.set(key, entry);
},
delete(key) {
entries.delete(key);
},
invalidateTags(tags) {
const requested = new Set(tags);
let deleted = 0;
for (const [key, entry] of entries) {
if (!entry.tags.some((tag) => requested.has(tag))) continue;
entries.delete(key);
deleted += 1;
}
return deleted;
},
clear() {
entries.clear();
},
};
}invalidateCache removes matching loader-resolution entries and rendered responses. clearCache clears both layers. A custom cache can implement invalidateTags and clear; otherwise the handler deletes the entries it has observed in the current process.
Look up exact public types
Follow linked API names to their canonical TypeScript declarations and package boundaries.