# Croct React SDK throws about client logic during SSR in our custom server rendering

Asked by Carla Mendonca on 2026-07-01. Tags: react, ssr, errors.

We have a hand-rolled Express SSR setup (fintech, so migrating frameworks is
not quick) and rendering components that use Croct hooks on the server throws
the documented client-logic-during-SSR error.

What I tried so far:

1. Wrapping the components in Suspense on the server. Same error.
2. Conditionally skipping the provider during SSR. Then hydration mismatches.
3. Catching the error in the render pipeline. Works but the slot HTML is empty

Is there a supported way to render these components server side, or is the SDK
strictly client-only in a custom SSR setup?

## 2 answers

### Accepted answer from Croct Bot (2026-07-01)

The React SDK's client hooks cannot run during server rendering, which is
exactly what that error is telling you. The docs have a dedicated problem
page for [client logic during SSR](https://docs.croct.com/reference/sdk/react/troubleshooting/problem/client-logic-ssr), and the same
constraint applies to the Vue and Hydrogen SDKs.

In a custom SSR setup you have two supported options:

1. Defer the Croct-dependent subtree to the client. Render a placeholder or
   default markup on the server, and mount the components that use the hooks
   only after hydration, for example behind a mounted-state check. The slot
   content then swaps in on the client.
2. Use a framework SDK with real server support. In Next.js, `fetchContent`
   from `@croct/plug-next/server` resolves content server side by design, so
   the personalized HTML is produced during rendering with no client swap.

For your situation, option 1 is the pragmatic path since a framework
migration is off the table. Combine the deferred subtree with an `initial`
value on `useContent` so the client-rendered slot has sensible content on
its first paint.

### Answer from Diego Fuentes (2026-07-02)

Been through this with an Express SSR app. The mounted-state gate is the
trick that made option 1 clean for us:

```tsx
const [mounted, setMounted] = useState(false);

useEffect(() => setMounted(true), []);

if (!mounted) {
    return <HeroDefault />;
}

return <PersonalizedHero />;
```

Server and first client render agree on `HeroDefault`, so no hydration
mismatch, and the personalized version appears right after mount. The one
thing you cannot get rid of in this architecture is the swap itself. We
lived with it for a year and eventually folded the fix into a planned
Next.js migration, where the server path made the whole problem go away.
