Navigated to /docs/core/rendering-head-and-hydration

Rendering, head, and hydration

Choose SSR or CSR, compose escaped route metadata, understand head cleanup, and keep private values out of browser-readable hydration state.

Treat SSR as the default

The route chain resolves to one PageRenderMode. Omitting the named render export selects SSR; only render = "csr" selects client-only route rendering.

ModeRoute declarationServerBrowser
SSRNo render exportMiddleware, eligible loaders, head, layouts, and the page render for the request.The same tree hydrates, then later navigation resolves in the browser.
CSRexport const render = "csr"Tavo.js renders the configured CSR fallback and static head contributions only.The route resolves and renders after client boot.
  • A CSR selection anywhere in the route module chain makes the resolved route CSR.

  • Static generation, revalidation, vary, cache tags, and static params are incompatible with CSR and are ignored with manifest diagnostics.

  • Use CSR only when the route cannot produce useful request HTML. Client interactivity does not require CSR; SSR pages hydrate into interactive components.

  • Dynamic head functions do not run on the server for CSR routes because route data is not resolved there.

Return escaped TSX from head

A named head export returns PageHeadExport: escaped TSX for normal metadata or a PageHead object for response status and document attributes.

TSX
tsximport { Seo } from "@tavojs/core";

type Project = {
  name: string;
  summary: string;
};

export function head({
  data
}: {
  data: Project | null;
}) {
  const title = data ? `${data.name} · Projects` : "Project";
  return (
    <>
      <title>{title}</title>
      <Seo
        title={title}
        description={data?.summary}
        robots="index,follow"
      />
    </>
  );
}
  • TSX children and attributes are escaped by the renderer. Prefer TSX and Seo for every normal metadata contribution.

  • The hard raw-string boundary is unsafeHeadHtml in a PageHead object or Head component. There is no raw head string alias.

  • unsafeHeadHtml is inserted without escaping. Never concatenate user, loader, request, database, translation, or CMS values into it unless a trusted sanitizer establishes the complete HTML policy.

  • head may also return title, status, htmlAttributes, and bodyAttributes in a PageHead object.

Predict head precedence and browser cleanup

  • Head contributions resolve from outer layouts to inner layouts, then the page.

  • A dynamic layout head receives that layout's own loader data and error. The page head receives page data and the page-loader error.

  • Later title, status, htmlAttributes, and bodyAttributes values win for the same field.

  • Managed Seo entries are deduplicated by their framework key with the later value winning. Unkeyed head nodes preserve contribution order.

  • On browser navigation, Tavo.js removes nodes owned by the previous route, applies the next route's nodes and attributes, and restores the document fallback title when the next route has no title.

Assume hydration state is public

SSR sends enough state for the browser to adopt the server-rendered tree. Anyone who receives the HTML can read this serialized state, including values that are not visibly rendered. Its ordered route data entries describe the resolved layout and page layers.

StateBrowser exposure
Page dataSerialized for the resolved page.
layers and layerDataSerialized layout and page loader results, both ordered and keyed.
Store snapshotsSerialized when included in the document hydration state.
Plugin stateSerialized when a plugin contributes hydration state.
ErrorsHydration error details are redacted to a generic internal-server message.
  • Return the minimum browser-safe shape from loaders. Keep tokens, session internals, credentials, private profile fields, and database records on the server.

  • Server-only execution does not make a returned loader value secret; SSR loader output can still be serialized.

  • Review nested layout data as carefully as page data because layerData exposes successful results by ID.

  • Redaction is a failure safeguard, not a reason to pass rich server exceptions into view props or custom error output.

Keep the server and client trees compatible

  • Render deterministic initial output from the same route data and serialized store state on both sides.

  • Move DOM reads, browser storage, timers, observers, and subscriptions into client lifecycle hooks.

  • Do not branch initial markup on Date.now(), Math.random(), locale defaults, viewport measurements, or undocumented globals.

  • A hydration mismatch is a correctness failure: fix the divergent input instead of suppressing the warning.

  • Clean up head ownership, controller work, and subscriptions when navigation replaces the route.

Verify source HTML and hydrated behavior

BASH
bashnpx tavo inspect route /projects/example --json
npx tavo build
PORT=4174 node .tavo/build/server/start.mjs
  • Inspect the raw document response to confirm SSR content, status, escaped metadata, and the absence of private values.

  • Inspect the live head before and after client navigation to confirm stale title, meta, html attributes, and body attributes are removed or replaced.

  • Hydrate with browser console errors treated as test failures.

  • Test a CSR route separately: the raw response should contain the chosen fallback and only static head contributions.

Look up exact public types

Follow linked API names to their canonical TypeScript declarations and package boundaries.