Studio Docs

When to Use

Symptom-mapped diagnostic for SSR / RSC render failures with <StudioComponent>. Covers the “use client” boundary, registry singleton, lazy-component loading, and useData fallback.

Use when the app fails to render compositions on the server. Signals: "Attempted to call Page() from the server", "Element type is invalid", useData() warning, "is registered as lazy", "is not registered" from the server bundle, hydration mismatches. If the symptom is ambiguous, route via troubleshoot first. Do NOT use for canvas iframe issues (troubleshoot-canvas) or binding issues (troubleshoot-data-binding).

Mandatory auth preflight: settle the credential before the first API call. Resolve it OAuth-first per authenticate-cma: CS_OAUTH_ACCESS_TOKEN, else the Contentstack MCP’s stored session. Never ask the user for a session authtoken. If nothing resolves, or a refresh fails with 400 invalid_refresh_token, hand them ! CONTENTSTACK_REGION=<code> npx @contentstack/mcp --auth (it needs a TTY and a browser, so it cannot be run for them) and wait. 403 error_code 316 is a valid credential aimed at another org: fix the org or the api_key, do not re-authenticate.

Troubleshoot Studio SSR / RSC Rendering Failures

Context

<StudioComponent> and the SDK’s built-in components (Page, Section, Text, Repeater, ConditionBlock, SectionComposition, etc.) are "use client" modules. When integrated into a server-rendered tree (especially Next.js App Router with React Server Components), three boundaries must be correctly drawn:

  1. The renderer entry must be "use client": <StudioComponent> uses React hooks and CSS-in-JS that don’t survive in a Server Component.
  2. The built-in basics must register on the client side only: importing them from the server-safe entry would place them in the consumer’s server module graph as client-references, which Next then fails to call at runtime.
  3. The registry must be shared across server and client module graphs: RSC server and client are separate bundles. A module-scoped let instance lives in only one graph.

The SDK ships fixes for each boundary internally (client-only built-ins isolation, shared cross-bundle registry, tolerant data-context fallback). This skill maps each symptom to its failing boundary so you can upgrade or fix the consumer-side wrapping.

Task

  1. Capture the SDK version the host app uses: grep '"@contentstack/studio-react"\\|"@contentstack/studio-client"\\|"@contentstack/studio-registry"' package.json. Several fixes referenced below are recent. Confirm the installed version against the package CHANGELOG.
  2. Identify the surface: server build error (during compile / first SSR pass) vs. runtime browser error vs. runtime server error in production. The matrix below distinguishes them.
  3. Match the symptom to a row.
  4. Run “Check first.” If positive, propose the fix. Most fixes are consumer-side wrapping. A few require an SDK upgrade.
  5. Report four things: the symptom, the SDK boundary that is failing, the minimum SDK version (if an upgrade) or the consumer-side fix, and what to verify afterwards.

Symptom to mechanism matrix

SymptomFailing boundaryCheck firstFix
”Attempted to call Page() from the server” (or Section(), Text(), Repeater(), any basic)The basics package landed in the consumer’s server module graph as client-references. The renderer’s registry lookup then tries to call them via JSX. Server can’t.Trace the import. Does anything in the server-rendered tree transitively import @contentstack/studio-react-components (the basics package) outside a "use client" boundary?The SDK isolates the basics behind a "use client" boundary internally. Recent @contentstack/studio-react versions ship this fix. Upgrade and check the CHANGELOG for the basics-isolation entry. On the consumer side: do not import the basics package from server code, and wrap <StudioComponent> in a "use client" child component.
Console warning: [Composable Studio SDK] useData() was called outside a DataCtxProvider. (dev only, client only)A basic component (Repeater / ConditionBlock / SectionComposition) hit the SSR pass before DataCtxProvider attached. The hook returns EMPTY_DATA and warns.Confirm: is the warning in dev only? In prod is the page rendering correctly post-hydration?If post-hydration render is correct, the warning is expected and benign: SSR placeholder, then hydration populates real data. If post-hydration is still wrong, see troubleshoot-data-binding (the binding itself is broken). If the warning appears in production console, that’s an SDK assertion bug. Escalate.
Hydration mismatch around the composition tree (React error about HTML mismatching)<StudioComponent> (or its containing component) is being rendered as a Server Component. The renderer uses hooks + client-only CSS-in-JS and is a "use client" module. Rendering it server-side produces HTML the client can’t reconcile.grep -n '"use client"' app/<your-route>/page.tsx. If absent and you’re calling <StudioComponent> directly: that’s the bug.Wrap <StudioComponent> in a tiny "use client" child (per configure-csr-vs-ssr skill). The parent Server Component does the data fetch (sdk.fetchCompositionData) and passes specOptions to the wrapper.
Error: Component '<name>' is registered as lazy but hasn't been loaded yet. This usually happens when: 1. fetchSpec() was not awaited before renderingThe component was registered LAZILY. Both lazy entry points hit this same code path: (a) registerComponent({ component: () => import("./Foo") }), any arity-0 function in component: that returns a Promise is treated as lazy. (b) registerLazyComponent({...config}, () => import("./Foo")), the explicit-signature form. The SDK does not auto-load on access. The loader runs explicitly during fetchSpec. If <StudioComponent> mounts before fetchSpec (or fetchCompositionData) has been awaited, the registry knows the component exists but the React.lazy bundle isn’t downloaded yet, and this throws.Confirm: is fetchSpec (or its wrapper fetchCompositionData) awaited before <StudioComponent> mounts? Note this affects both lazy-registration shapes. Fix is the same for both.Always await sdk.fetchCompositionData(...) before passing specOptions to <StudioComponent>. If using useCompositionData, render a loading state until isLoading === false. The contract is the same whether you registered via the arity-0-thunk form of registerComponent or the explicit registerLazyComponent form.
Component with type ‘X’ is not registered (emitted from the server bundle / RSC fetch)Pre-fix, the component registry was module-scoped. RSC server and client bundles had separate instances, so server-fetched specs couldn’t find user-registered components. The SDK’s fix shares the registry across both module graphs at runtime.Confirm the installed @contentstack/studio-registry version is recent (check its CHANGELOG for “shared registry” / “globalThis-backed singleton” entries).Upgrade studio-registry and the packages that depend on it to a version that ships the shared-registry fix. After upgrade, registration done at module-init time is visible to both server and client bundles within the same JS runtime.
Component with type ‘X’ is not registered where X is a built-in (page, section, text, repeater, condition-block, section-slot, box), on the visitor render path (<StudioComponent> in SSR/RSC, or any non-canvas public route)Pre-patch SDK (1.3.1 / 1.5.1 and earlier) didn’t auto-register built-ins on the visitor render path. Only the authoring canvas did. PR #851 (fix/builtin-ssr-registration) restores auto-registration. Ships in the next 1.3.x / 1.5.x patch.npm ls @contentstack/studio-react. Is the installed version older than the patch that contains PR #851?Upgrade @contentstack/studio-react to the patch release containing PR #851. Don’t hand-register internals as a workaround. Auto-register is back on the visitor path.
Component with type ‘X’ is not registered where X is a built-in, only on routes that mount <StudioCanvas> and not <StudioComponent>Distinct from the visitor-path issue above: older SDK versions also had a shared-registry tree-shake gap where canvas-only routes could load with an empty built-in registry. A separate fix in recent @contentstack/studio-react versions covers this case.Open a canvas-only route in a production build and check console for “is not registered” errors on built-in types. If present on a canvas route (not a visitor route), this is the canvas-side fix you need.Upgrade @contentstack/studio-react to a version that includes the canvas-route built-in fix (check its CHANGELOG).
<StudioCanvas> import explodes during server build<StudioCanvas> is the edit-mode iframe overlay, client-only by design. Importing it into a Server Component pulls the visual-editor SDK into the server bundle.Was the canvas route file marked "use client"? Check at the top of the file.Mark the canvas route "use client" (per setup-template-preview-routes skill / setup-section-preview skill). Or use Next’s dynamic(() => import('...'), { ssr: false }) form.
In SSR, fetched composition resolves to the wrong locale / variant / preview entryOn the server there is no window.location.search. sdk.fetchCompositionData REQUIRES the consumer to pass searchQuery explicitly so iframe/preview parameters flow into the CDA call. Skip it and you get the default fetch.Inspect the server-side call site. Is searchQuery being passed? await sdk.fetchCompositionData({ url, searchQuery }, { locale }).Extract searchQuery from the request and pass it. Pattern documented in configure-csr-vs-ssr.md. Not an SDK bug: a consumer integration step.
Next.js App Router route 500s with Cannot read properties of null (reading 'useContext') at useData → RepeaterPreviewAn SDK basic executed during the RSC server prerender against a React instance with a null dispatcher (Next’s app-page runtime). Trigger: transpilePackages includes @contentstack/studio-*, which pulls the basics in as real components (defeating the renderer’s isClientReference deferral), so they run on the server and hit a null React dispatcher.grep -n "transpilePackages" next.config.{js,mjs,ts}. Does it list any @contentstack/studio-* package? Is the route under app/?Remove @contentstack/studio-* from transpilePackages on App Router (restores deferral: no crash, but the composition is then client-rendered, not SSR’d). For true SSR on Next, switch the route to Pages Router (renderToString pipeline). See configure-csr-vs-ssr for the corrected decision table.
App Router route renders in the browser but view-source / <body> (after stripping <script>) is empty of composition DOM, bound content only in self.__next_fBy SDK design: the renderer detects every built-in as Symbol.for("react.client.reference") during the RSC server pass and emits <data-cs-defer-builtin> placeholders. Real render happens on client hydration. This is CSR-equivalent, not true SSR.View page source. Look for data-cs-defer-builtin attributes or the absence of repeater iteration values.Two options: (a) accept App Router = client-rendered for the composition (fine for non-SEO surfaces). (b) for true SSR, move the route to Pages Router, Vite custom-server (renderToString + hydrateRoot, see composable-studio-sdk/test-resources/ssr-react), or Remix, all of which run the basics through renderToString server-side.
Pages Router next dev only: Repeater items render field defaults (“Your text here”) on some boots, real data on othersPer-boot dev race: recompile + module-init order + React StrictMode double-invoke can serialize before registrations land. Prod build doesn’t have this race.Reproduce on next build && next start. Does the issue persist?Don’t judge SSR from next dev. Verify with a prod build. Deploy from prod artifacts. (Not an SDK bug: dev-server timing.)
Infinite _next/data/<buildId>/<route>.json (Pages Router) or RSC payload (App Router) refetch loop: Network tab fires hundreds of times per editContentstackLivePreview.onEntryChange(cb) invokes cb once at register-time. If cb = () => router.replace(asPath) / router.refresh() is wired in a useEffect(..., [router]), the router-identity change re-runs the effect, which unsubscribes and resubscribes, which fires the register-time callback, which calls router.replace, and the loop never ends. This is a Live Preview wiring bug, not an SSR bug per se.Inspect the onEntryChange wiring. What are the effect’s deps? Is firstFire skipped? Is unsubscribe wired?Replace with the loop-safe pattern from install-live-preview (step 7): useEffect(..., []) (subscribe ONCE), hold router in a useRef, skip the first (register-time) callback, unsubscribeOnEntryChange in cleanup. Reference: SDK’s test-resources/ssr-react/hooks/use-studio-spec-options.ts (skipFirstClientFetchRef).
In SSR / RSC, design tokens or fonts not applied on first paintThe renderer applies design tokens via goober CSS-in-JS at runtime, and loads Google Fonts via useEffect. SSR pass produces HTML without the runtime-applied styles. First paint can flash unstyled content.View page source on the SSR’d route. Are the :root CSS variables present?This is a known runtime-style limitation. Pre-rendering critical tokens via static CSS at the host-app level is the consumer-side mitigation. Not an SDK bug.
Section composition shows as unregisteredSection-composition UIDs are resolved at render time. If a section composition isn’t registered before its first paint, the lookup throws. The SDK auto-registers sections during fetchSpec. Failure here means fetchSpec ran but registration didn’t complete before render.Confirm fetchSpec was awaited. Check the spec response for the section composition’s UID.Await fetchSpec / fetchCompositionData before render. If the spec doesn’t contain the section composition’s UID, it wasn’t included in the composition’s fetched dependencies. Escalate to engineering with the parent composition UID + the missing section composition UID.

When the symptom maps to none of the above

Likely-but-unverified causes (escalate rather than guess):

  • Tree-shake gotchas in non-Next bundlers (Vite SSR, Astro, custom Webpack with sideEffects: false): no general fix path documented. The bootstrap pattern depends on the renderer being eagerly evaluated, which some bundlers won’t do for side-effect-only imports.
  • use server directives wrongly applied to SDK code: never add use server to SDK files. The SDK does not export Server Actions.
  • Mixed SDK package versions: studio-registry@1.X + studio-react@1.Y where the versions disagree on the singleton API. Check that the versions installed satisfy the SDK’s peerDependencies.

When escalating, share: framework + version, every @contentstack/studio-* package’s exact installed version (npm ls @contentstack/studio-react @contentstack/studio-registry @contentstack/studio-client), the route file with full "use client" boundary structure, and the error stack trace.

What This Skill is NOT