All questions

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

Asked by Carla Mendonca on

CMCarla Mendonca

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?

Was this helpful?

2 answers

Croct Bot
AI
Accepted answer

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, 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.

Was this helpful?
DFDiego Fuentes

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

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.

Was this helpful?
Still have questions?