SSR and hydration
Orbz separates server-safe definitions from browser registration. The package
does not evaluate an HTMLElement subclass at module scope, and
defineOrbz() checks for DOM APIs before doing any work.
Entry-point behavior
| Import | Purpose | Server behavior |
|---|---|---|
@neongate-ai/orbz | Types, constants, helpers, and explicit registration | Safe; no automatic registration |
@neongate-ai/orbz/browser | Native browser entry | Calls guarded defineOrbz(); no-op without a Custom Elements registry |
@neongate-ai/orbz/react | Strict React/Next adapter | Import-safe; registers after the adapter mounts |
@neongate-ai/orbz/standalone | Self-contained browser/CDN build | Uses the browser registration path |
Native server-rendered markup
A server can emit an unknown custom element as ordinary HTML:
<div role="status" aria-live="polite">
<orb-z state="idle" preset="neongate"></orb-z>
<span>Assistant is idle</span>
</div>Load the browser entry in a client entry module. When registration runs, the existing element upgrades in place.
import "@neongate-ai/orbz/browser";For explicit control, use the root entry:
import { defineOrbz } from "@neongate-ai/orbz";
defineOrbz();defineOrbz() is idempotent. It returns the existing constructor if orb-z
has already been registered and returns undefined when registration is not
available.
React and Next.js
Use the adapter in a Client Component:
"use client";
import { Orbz } from "@neongate-ai/orbz/react";
export function AssistantPresence() {
return (
<div role="status">
<Orbz state="idle" preset="neongate" size={300} />
<span>Assistant is idle</span>
</div>
);
}A Server Component can import and render AssistantPresence; the client
boundary stays at the smallest component that needs Orbz and interactive state.
You do not need to disable server rendering for the whole page.
Keep the first render deterministic
Hydration problems come from server and client markup disagreeing, not from the custom element itself. Use the same initial state and appearance on both sides, then read browser-only information after mount.
"use client";
import { useEffect, useState } from "react";
import { Orbz } from "@neongate-ai/orbz/react";
export function ConnectedPresence() {
const [state, setState] = useState<"idle" | "asleep">("idle");
useEffect(() => {
setState(navigator.onLine ? "idle" : "asleep");
}, []);
return <Orbz state={state} preset="neongate" />;
}Avoid deriving initial attributes from window, matchMedia, random values,
or current time during render. Orbz already handles the system reduced-motion
media query internally after it connects.
Registration placement
Import /browser once from a stable browser entry or call defineOrbz() from
your client bootstrap. Multiple calls are safe, but a single known registration
point makes ownership and versioning clearer.
In a microfrontend page, remember that the first registered implementation of
orb-z wins. Coordinate package versions even though duplicate registration
calls are guarded. See Orbz in microfrontends.
Progressive behavior
Before JavaScript loads, <orb-z> has no rendered sphere because it has not
been upgraded. Surrounding semantic HTML still works. This is why status text
and controls should not live inside or depend on the visual component.
If a visible no-JavaScript placeholder is important, provide it in the host layout and remove it when your client application is ready. Do not attempt to inject fallback content into Orbz; its strict public API does not accept children.
SSR checklist
- Import types and constants from the root entry.
- Use
/browserin a native client bootstrap or/reactin a Client Component. - Keep server and first-client attributes identical.
- Add live status text outside the element.
- Do not access the closed Shadow DOM during hydration.
- Do not disable SSR for an entire route just to render Orbz.
- Coordinate the registered version when several bundles share a page.