Step 1 — Build
Compute a SHA-256 checksum of the full mangle map at build time.
When using SSR (Next.js, Remix, Astro), the server renders HTML with mangled
class names (z, y, x). The client must use the same mangle map to
hydrate correctly. CSSzyx uses SHA-256 checksums to verify this.
Without protection, a stale CDN cache could serve HTML with an old mangle map
while the browser downloads a new JS bundle with a different map. The result:
class z means p-4 on the server but bg-blue-500 on the client. Your
app silently renders with broken styles.
CSSzyx detects this and aborts hydration before the mismatch causes damage.
Step 1 — Build
Compute a SHA-256 checksum of the full mangle map at build time.
Step 2 — Inject
Embed the checksum in the <html data-sz-checksum="..."> attribute.
Step 3 — Verify
Client validates the checksum before the first React hydration.
<!-- Server output includes checksum and mangle map --><html data-sz-checksum="a3f9c2..."> <script id="__CSSZYX_MANGLE_MAP__" type="application/json"> { "class:p-4": "z", "class:bg-blue-500": "y", "class:hover:bg-blue-600": "x" } </script></html>The client runtime calls verifyMangleChecksum(expectedChecksum) before
hydration. If the expected checksum doesn’t match the checksum embedded on
<html data-sz-checksum="...">, the abort protocol fires.
When production.mangleVars or production.mangleGlobalVars emits CSS
variable aliases, the same checksum map also includes CSS-variable entries such
as var:--_sz-p:--cz or var:--brand-primary. The debug helper keeps a
separate window.__csszyx.varMangleMap for inspection.
// What happens on mismatch (automatically, no code needed)if (!verifyMangleChecksum(expectedChecksum)) { abortHydration(); // Preserve SSR HTML, block React hydration}Enable checksum injection in the plugin
...csszyx({ production: { mangle: true, injectChecksum: true, // ← enables the checksum in HTML },})Initialize the runtime at app startup
// src/main.tsx (or app/layout.tsx for Next.js)import { initRuntime } from "@csszyx/runtime";
initRuntime({ development: process.env.NODE_ENV === "development", strictHydration: true,});(Optional) Per-element recovery via szRecover
The legacy global allowCSRRecovery flag was replaced by the
per-element szRecover JSX attribute (see next section). It scopes
recovery to subtrees where it’s actually safe instead of opting the
whole page in.
szRecoverBy default a hydration mismatch aborts the entire page — the safe choice
for a production app you don’t want to render with broken styles. For
specific subtrees where re-rendering on the client is cheaper than
aborting, opt in per-element with the szRecover JSX attribute.
<section szRecover="csr"> {/* Hydration mismatch in this subtree triggers a client re-render instead of aborting the whole document. */} <UserGeneratedContent html={post.body} /></section>
<aside szRecover="dev-only"> {/* Same recovery behaviour, but ONLY in development builds. Stripped from the production manifest at build time. */} <DebugPanel /></aside>Build. The csszyx unplugin scans every JSX element with szRecover
and tags it with data-sz-recovery-token="<12-hex>". The token is a
deterministic hash of ${file}:${line}:${col}:${elementType} — stable
across rebuilds, so HMR doesn’t churn the manifest.
SSR HTML. The unplugin injects a single manifest script into
<head> containing every emitted token:
<script id="__SZ_RECOVERY_MANIFEST__" type="application/json"> { "buildId": "…", "checksum": "…", "mangleChecksum": "…", "tokens": { "a3b9…": { "mode": "csr", "component": "section", "path": "…" } } }</script>checksum protects the recovery token set. mangleChecksum is the
value compared with the page’s data-sz-checksum hydration checksum.
Hydration. @csszyx/runtime/verify reads the manifest, then
verifyRecoveryToken(element, manifest) matches each element’s
token against an entry. A valid match permits client recovery for
that subtree; an unknown token rejects.
csr vs dev-only| Mode | Dev manifest | Prod manifest | When to use |
|---|---|---|---|
csr | included | included | Subtrees that legitimately may render differently on client (auth-gated, locale-aware, A/B) |
dev-only | included | stripped | Debug overlays, dev-mode banners — the build emits a single rolled-up warning listing the stripped paths |
Production strips dev-only because the runtime’s __SZ_ALLOW_CSR_RECOVERY__
flag is dev-mode only; shipping these tokens would just add bytes the
runtime would refuse anyway.
szRecover is typed on React.HTMLAttributes via @csszyx/types/jsx.
The triple-slash reference in the umbrella csszyx package’s type
output means importing csszyx anywhere in the project surfaces the
prop with full IntelliSense — no extra tsconfig wiring:
<section szRecover="csr">…</section> // ✓ valid<section szRecover="dev-only">…</section> // ✓ valid<section szRecover="ssr">…</section> // ✗ Type '"ssr"' is not assignableimport { initRuntime, startHydration, endHydration, abortHydration, verifyMangleChecksum, verifyMangleMapIntegrity, getHydrationErrors, isHydrationAborted,} from "@csszyx/runtime";
// Start hydration sessionstartHydration();
// Verify checksum (throws on mismatch if strictHydration: true)const isValid = verifyMangleChecksum(expectedChecksum);
// Check if hydration was abortedif (isHydrationAborted()) { // Preserve SSR HTML}
// Get all hydration errorsconst errors = getHydrationErrors();Enable debug logging to see what’s happening:
initRuntime({ debug: true });This logs:
import type { HydrationErrorType } from "@csszyx/runtime";
// Error types:// 'checksum_mismatch' — SHA-256 mismatch between server/client// '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