Skip to main content
Server-side rendering introduces a timing challenge: the server has to resolve parameters during the request lifecycle so the first paint is correct, but the client also needs the assignments for interactive behaviour. If you fetch the bundle twice — once on the server, once on the client — hydration can re-resolve to different values and cause a visible swap. The pattern is always the same:
  1. Fetch the bundle once on the server.
  2. Pass it to the client via your framework’s data-loading mechanism.
  3. Initialize the client SDK with the server-provided bundle as localConfig.
  4. The client uses the same bundle and resolves identically — no second fetch on hydration.

SvelteKit

Layout load (server)

loadTrafficalBundle respects standard HTTP caching, so subsequent SSR requests within the cache TTL benefit from SvelteKit’s fetch cache.

Root layout (server + client)

On the Svelte SDK the server-fetched bundle goes through initialBundle (not localConfig). initialBundle is what marks the provider ready during SSR and lets it resolve on the server so the first paint is correct. localConfig is the separate build-time/offline fallback bundle.

Page

The server resolves with data.traffical.bundle and renders the correct allocation. The client picks up the same bundle and resolves identically. No swap.

Next.js (App Router / RSC)

The React SDK has no @traffical/react/server entry point. Fetch the config bundle directly from the SDK config endpoint — the same URL the client SDK calls — and pass it as localConfig:
Wrapping the fetch in Next.js’s fetch integration (next: { revalidate }) caches it, so subsequent renders within the cache window don’t re-fetch.

Next.js (Pages Router)

Why this works

  • Same bundle, same hash, same answer. Both server and client read the same bundle and run the same SHA-256 v2 bucketing. The same userId always resolves to the same allocation.
  • One network fetch. The server fetches; the client receives the bundle inline. No second fetch on hydration.
  • First paint is correct. Because the server resolves before rendering, the user sees the right allocation immediately. No flash of original content.

Pitfalls

  • Mismatched user IDs. If the server reads userId from a session cookie but the client reads it from a different source, the two will resolve to different buckets and you’ll see a swap on hydration. Make sure both sides use the same value.
  • Use two keys — one per side. The client-side TrafficalProvider takes a publishable SDK key (traffical_pk_...), which is compiled into the browser bundle and is safe there. The server-side bundle fetch (the plain fetch on Next.js, loadTrafficalBundle on SvelteKit) takes a server-side SDK key (traffical_sk_...), read from a private environment variable that is never exposed to the client. Expose the publishable key through your framework’s public env mechanism (NEXT_PUBLIC_*, $env/static/public) and keep the server key in the private one ($env/static/private, a non-NEXT_PUBLIC_ variable). Never route a traffical_sk_... key through a public variable — that ships it to every visitor. Keep management keys out of rendering code entirely.
  • Stale bundle on long-lived pages. The client refreshes the bundle every 60s by default. SPAs running for hours will pick up new policies automatically. For static pages, a hard reload is enough.

Next steps

SSR + hydration pattern

Full end-to-end example.

React SDK

Provider, hooks, options.

Svelte SDK

Stores, context, runes.