Server rendering
Shadow-root tradeoffs, rendering to a string, and loading documents at runtime.
<Joy> renders into its own shadow root. On the server that is a declarative shadow root (<template shadowrootmode>). The browser attaches it while parsing the HTML, which means React cannot hydrate that subtree afterwards. Two approaches work around this.
Static or string rendering
renderToStaticMarkup(<Joy spec={spec} />) produces the declarative shadow root. Mount it with element.setHTMLUnsafe(html) so the browser attaches it. No hydration is involved, which suits static generation or a server function that returns markup.
Inside a hydrated app
For Next.js, Waku, or Remix, render <Joy> on the client so its shadow root attaches client-side rather than hydrating. Gate it on a mounted flag:
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
return mounted ? <Joy spec={spec} /> : null;Joy DOM's own website does exactly this for its previews.
Rendering to a string
For non-component contexts such as a server function or 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 is not part of your build, fetch it and validate at the boundary:
import { , } from "react";
import { } from "@joy-dom/react";
import { , type } from "@joy-dom/core";
export function ({ }: { : string }) {
const [, ] = < | null>(null);
(() => {
()
.(() => .())
.(() => .())
.();
}, []);
if (!) return null;
return < ={} />;
}SpecSchema.parse at the boundary makes a malformed document fail fast with a readable Zod error.
Integration patterns
| Document source | Approach |
|---|---|
| Bundled with the app | Import the JSON directly (import card from "./card.json"). Bundlers tree-shake to the documents you reference. |
| Served from an API | Fetch and validate with SpecSchema, as above. |
| Generated per user | Render on the server and pass to the client as initial props. Validate on the server boundary, not the client. |