Skip to Content
GuidesSSR & hydration

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

ImportPurposeServer behavior
@neongate-ai/orbzTypes, constants, helpers, and explicit registrationSafe; no automatic registration
@neongate-ai/orbz/browserNative browser entryCalls guarded defineOrbz(); no-op without a Custom Elements registry
@neongate-ai/orbz/reactStrict React/Next adapterImport-safe; registers after the adapter mounts
@neongate-ai/orbz/standaloneSelf-contained browser/CDN buildUses 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 /browser in a native client bootstrap or /react in 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.
Last updated on