Skip to content

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.

  1. startHydration() — begin hydration session

  2. loadMangleMapFromDOM() — read the injected checksum mangle map from the <script> tag

  3. verifyMangleChecksum() — validate SHA-256 against data-sz-checksum

  4. If invalidabortHydration() preserves SSR HTML and CSS intact

  5. If valid — React hydrates normally

  6. endHydration() — close session, emit audit log

Opens a hydration session. Call before any validation or React render.

function startHydration(): void;

Closes the current hydration session.

function endHydration(): void;

Aborts hydration. Blocks React from taking over the DOM.

function abortHydration(): void;

Returns true if abortHydration() has been called.

function isHydrationAborted(): boolean;

Returns true if a hydration session is active.

function isHydrating(): boolean;

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.

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;

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.

Checks that a single mangled class exists in the mangle map.

function validateHydrationClass(mangledClass: string, map: MangleMap): boolean;

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;
}

Clears the error list. Useful for testing.

function clearHydrationErrors(): void;
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 invalid

Recovery tokens allow trusted mangle maps to be accepted even when the main checksum check fails. Available in development only.

Verifies a recovery token against a mangle map.

function verifyRecoveryToken(token: string, map: MangleMap): VerificationResult;
interface VerificationResult {
valid: boolean;
mode: RecoveryMode;
expiresAt?: number;
}

Returns true if a recovery token is present in the DOM.

function hasRecoveryToken(): boolean;

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;

Returns true when running in an SSR context (no DOM available).

function isSSREnvironment(): boolean;

Returns the current SSR context object.

function getSSRContext(): SSRContext | null;
interface SSRContext {
buildId: string;
checksum: string;
timestamp: number;
hasRecoveryTokens: boolean;
}

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 token
Object.entries(classes).find(([, token]) => token === 'z'); // → the original

build.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.

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();