SSR Hydration API
The hydration API provides fine-grained control over SSR mismatch detection,
abort protocol, and recovery. Most apps only need initRuntime() — the rest
is available for advanced use cases.
Hydration Lifecycle
Section titled “Hydration Lifecycle”-
startHydration()— begin hydration session -
loadMangleMapFromDOM()— read the injected checksum mangle map from the<script>tag -
verifyMangleChecksum()— validate SHA-256 againstdata-sz-checksum -
If invalid —
abortHydration()preserves SSR HTML and CSS intact -
If valid — React hydrates normally
-
endHydration()— close session, emit audit log
Core Functions
Section titled “Core Functions”startHydration()
Section titled “startHydration()”Opens a hydration session. Call before any validation or React render.
function startHydration(): void;endHydration()
Section titled “endHydration()”Closes the current hydration session.
function endHydration(): void;abortHydration()
Section titled “abortHydration()”Aborts hydration. Blocks React from taking over the DOM.
function abortHydration(): void;isHydrationAborted()
Section titled “isHydrationAborted()”Returns true if abortHydration() has been called.
function isHydrationAborted(): boolean;isHydrating()
Section titled “isHydrating()”Returns true if a hydration session is active.
function isHydrating(): boolean;Mangle Map Verification
Section titled “Mangle Map Verification”loadMangleMapFromDOM()
Section titled “loadMangleMapFromDOM()”Reads the injected checksum mangle map from the #__CSSZYX_MANGLE_MAP__
script tag in the DOM. The legacy #__SZ_MANGLE_MAP__ id is still accepted
for older builds.
function loadMangleMapFromDOM(): MangleMap | null;
type MangleMap = Record<string, string>; // { 'class:p-4': 'z', 'var:--_sz-p:--cz': '--cz' }When production.mangleVars or production.mangleGlobalVars emits CSS
variable aliases, the checksum payload includes both class and CSS-variable
entries. One original dynamic variable can map to more than one mangled name,
so one-to-many variable entries are namespaced with the mangled name in the
checksum key. Global aliases use the same var: namespace, for example
var:--brand-primary.
verifyMangleChecksum(expectedChecksum)
Section titled “verifyMangleChecksum(expectedChecksum)”Compares the expected mangle checksum with the checksum the build embedded on
<html>. Reads data-sz-checksum, or data-sz-cs when production.minify
wrote the short name — which it does by default. Returns true if they match.
function verifyMangleChecksum(expectedChecksum: string): boolean;verifyMangleMapIntegrity()
Section titled “verifyMangleMapIntegrity()”Validates the injected mangle map against the embedded checksum.
function verifyMangleMapIntegrity(): boolean;Synchronous, so it can only recompute the checksum through the Rust core’s
verify_mangle_checksum global. Without that global it validates the map’s
shape, accepts it, and says so only in development — a true from this
function does not on its own mean a checksum was recomputed. Prefer
verifyMangleChecksumAsync, which recomputes
through the Web Crypto API and needs no WebAssembly and no CSP exception. It
answers false and warns where Web Crypto is unavailable — an insecure
context — rather than claiming a match it could not compute.
validateHydrationClass(mangledClass, map)
Section titled “validateHydrationClass(mangledClass, map)”Checks that a single mangled class exists in the mangle map.
function validateHydrationClass(mangledClass: string, map: MangleMap): boolean;Error Inspection
Section titled “Error Inspection”getHydrationErrors()
Section titled “getHydrationErrors()”Returns all recorded hydration errors for the current session.
function getHydrationErrors(): HydrationError[];
interface HydrationError { type: HydrationErrorType; message: string; class?: string; // The mangled class that failed (if applicable) timestamp: number;}clearHydrationErrors()
Section titled “clearHydrationErrors()”Clears the error list. Useful for testing.
function clearHydrationErrors(): void;Error Types
Section titled “Error Types”type HydrationErrorType = | "checksum_mismatch" // SHA-256 does not match | "missing_manifest" // No mangle map in DOM | "invalid_manifest" // Mangle map is malformed | "class_not_found" // Mangled class has no mapping | "token_invalid"; // Recovery token signature is invalidRecovery Tokens
Section titled “Recovery Tokens”Recovery tokens allow trusted mangle maps to be accepted even when the main checksum check fails. Available in development only.
verifyRecoveryToken(token, map)
Section titled “verifyRecoveryToken(token, map)”Verifies a recovery token against a mangle map.
function verifyRecoveryToken(token: string, map: MangleMap): VerificationResult;
interface VerificationResult { valid: boolean; mode: RecoveryMode; expiresAt?: number;}hasRecoveryToken()
Section titled “hasRecoveryToken()”Returns true if a recovery token is present in the DOM.
function hasRecoveryToken(): boolean;getRecoveryMode()
Section titled “getRecoveryMode()”Returns the current recovery mode.
type RecoveryMode = "none" | "csr" | "dev-only";function getRecoveryMode(): RecoveryMode;enableCSRRecovery() / disableCSRRecovery() / isCSRRecoveryAllowed()
Section titled “enableCSRRecovery() / disableCSRRecovery() / isCSRRecoveryAllowed()”Manually enable or disable client-side recovery.
function enableCSRRecovery(): void;function disableCSRRecovery(): void;function isCSRRecoveryAllowed(): boolean;SSR Context
Section titled “SSR Context”isSSREnvironment()
Section titled “isSSREnvironment()”Returns true when running in an SSR context (no DOM available).
function isSSREnvironment(): boolean;getSSRContext()
Section titled “getSSRContext()”Returns the current SSR context object.
function getSSRContext(): SSRContext | null;
interface SSRContext { buildId: string; checksum: string; timestamp: number; hasRecoveryTokens: boolean;}The Runtime Mangle Registry
Section titled “The Runtime Mangle Registry”A production-mangled build registers the mangle map with @csszyx/runtime from
a generated module inside the JS bundle — attached to every HTML entry ahead of
the app, and to every module that imports the runtime. The built HTML carries no
executable inline script on any lane, so the registration is covered by whatever
script-src already allows your bundle, and pages the build does not own (a
bundle embedded in a host shell) receive the map the same way.
mangleMap stays class-only, while varMangleMap contains CSS variable
mappings when production.mangleVars is enabled.
import { getMangleRegistry, installMangleRuntime } from '@csszyx/runtime';
interface MangleRegistry { mangleMap: Record<string, string>; varMangleMap: Record<string, string | string[]>; checksum: string; encode(originalClass: string): string | undefined; decode(mangledClass: string): string | undefined; encodeVar(originalVar: string): string | string[] | undefined; decodeVar(mangledVar: string): string[]; decodeGlobalVar(mangledVar: string): string | undefined; decodeAll(el: { className: string }): string[];}The runtime helpers (szr, szcn, szDecode) read the registry for their
mangle-aware paths; when it is absent they fall back to identity behavior.
getMangleRegistry() returns it (or null); installMangleRuntime() is what
the generated module calls and is idempotent per checksum.
window.__csszyx is opt-in: set production.mangleDebugGlobal: true and the
same registry object is assigned to the global for devtools inspection. Nothing
about correctness depends on it, and nothing else installs it.
Reading the map on a page you cannot rebuild
Section titled “Reading the map on a page you cannot rebuild”mangleDebugGlobal is a build option, so it cannot help with a build that is
already deployed. The census can: every build emits it into the HTML as inert
JSON, so devtools alone answers “what was this class originally?”.
// In the console, on any csszyx-built page:const map = JSON.parse(document.getElementById('__CSSZYX_MANGLE_MAP__').textContent);
// Class-only build: original → token. With mangleVars on, keys are namespaced// (`class:p-4`, `var:--_sz-p`), so strip the prefix first.const classes = Object.fromEntries( Object.entries(map) .filter(([key]) => !key.startsWith('var:')) .map(([key, token]) => [key.replace(/^class:/, ''), token]),);
classes['p-4']; // → the tokenObject.entries(classes).find(([, token]) => token === 'z'); // → the originalbuild.emitManifest writes the same data to csszyx-manifest.json next to the
build output, if you would rather read it server-side than in a browser.
Example: Manual Hydration Guard
Section titled “Example: Manual Hydration Guard”For advanced control over the hydration process:
import { startHydration, endHydration, abortHydration, loadMangleMapFromDOM, verifyMangleChecksum, isHydrationAborted,} from "@csszyx/runtime";
// In your app entry before React.hydrate()startHydration();
const mangleMap = loadMangleMapFromDOM();const checksum = document.documentElement.dataset.szChecksum;
if (!mangleMap || !checksum || !verifyMangleChecksum(checksum)) { abortHydration();}
if (!isHydrationAborted()) { // Safe to hydrate hydrateRoot(document.getElementById("root"), <App />);}
endHydration();