---
name: troubleshoot-ssr-rendering
---

## 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`](authenticate-cma.md): `CS_OAUTH_ACCESS_TOKEN`, else the Contentstack MCP's stored session: Read that skill for where it is, even with no MCP connected. **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

| Symptom | Failing boundary | Check first | Fix |
|---|---|---|---|
| **"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:** `[Contentstack Studio] 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](configure-csr-vs-ssr.md)). 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 rendering` | The 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**, or `COMPONENT_NOT_REGISTERED: Studio's own components are not registered: X`, 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) | The built-ins register themselves when the `@contentstack/studio-react` package root is first imported. The visitor path's fetching code never imported it, or a server-only bundle tree-shook it out. | Does the module that runs the fetch, or one it imports, contain `import "@contentstack/studio-react";`? | Add `import "@contentstack/studio-react";` once, above the fetch, in the bundle that runs it. Don't hand-register internals as a workaround. |
| **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-template-preview-routes.md) / `setup-section-preview` [skill](setup-section-preview.md)). Or use Next's `dynamic(() => import('...'), { ssr: false })` form. |
| **In SSR, fetched composition resolves to the wrong locale / variant / preview entry** | On 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`](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 → RepeaterPreview`** | An 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 SSR, use a `"use client"` wrapper (`configure-csr-vs-ssr` step 3). 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_f`** | By 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 SSR, use a `"use client"` wrapper, 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 others** | Per-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 edit | `ContentstackLivePreview.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`](install-live-preview.md) (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 paint** | The 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 unregistered** | Section-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

- Not a guide to setting up SSR from scratch. Use [`configure-csr-vs-ssr`](configure-csr-vs-ssr.md).
- Not a guide to live-site URL resolution failures (use [`troubleshoot-composition-resolution`](troubleshoot-composition-resolution.md)).
- Not a guide to canvas iframe failures (use [`troubleshoot-canvas`](troubleshoot-canvas.md)).
- Not a guide to per-binding data issues (use [`troubleshoot-data-binding`](troubleshoot-data-binding.md)).
