Server rendering
Shadow-root tradeoffs, rendering to a string, and loading documents at runtime.
<JoyDom> renders into its own shadow root. On the server that's a declarative
shadow root (<template shadowrootmode>); the browser attaches it while parsing
the HTML, which means React can't hydrate that subtree afterwards. So pick one:
Static / string rendering. renderToStaticMarkup(<JoyDom spec={spec} />) produces the
declarative shadow root; mount it with element.setHTMLUnsafe(html) so the browser attaches
it. No hydration involved — ideal for static generation or a server function returning markup.
Inside a hydrated app (Next.js, Waku, Remix). Render <JoyDom> on the client so its shadow
root is attached client-side, not hydrated. The simplest way is to gate it on a mounted flag
(const [m, setM] = useState(false); useEffect(() => setM(true), [])) and render {m ? <JoyDom … /> : null}. Joy DOM's own website does exactly this for its previews.
Rendering to a string
For non-component contexts — a server function, a test — render with react-dom/server:
const = (< ={} />);The markup contains a declarative shadow root (<template shadowrootmode>); mount it with element.setHTMLUnsafe(html) so the browser attaches the shadow.
Load documents at runtime
When the document isn't part of your build, fetch it and validate at the boundary:
import { , } from "react";
import { } from "@joy-dom/react";
import { , type Spec } from "@joy-dom/spec";
export function ({ }: { : string }) {
const [, ] = <Spec | null>(null);
(() => {
()
.(() => .())
.(() => .())
.();
}, []);
if (!) return null;
return < ={} />;
}Calling SpecSchema.parse at the boundary makes malformed documents fail fast with a readable Zod error.
Common integration patterns
Static documents bundled with the app. Import JSON directly: import card from "./card.json". Bundlers tree-shake; production payload only carries the documents you
reference.
SpecSchema as above.Per-user generated documents. Render on the server, pass to the client as initial props. Validate on the server boundary, not the client (cheaper, single source of truth).