Skip to main content

SSR safety

Every rune state class in @cstar.help/svelte checks typeof window === 'undefined' before wiring real-time subscriptions. SSR renders a loading state; the browser takes over on hydration. No export const ssr = false needed.

Why it matters

Real-time state classes used to crash during SSR because they tried to open SSE connections in Node. Setting ssr = false on every chat-bearing route was a footgun — broke Lighthouse, broke OG previews, and forced full client renders for surfaces that didn't need them. The state classes now defer subscribe-wiring until they detect a browser.

The pattern

src/routes/help/+page.svelte
<script>
  import { ArticlesState } from '@cstar.help/svelte';

  // Construction is safe on the server — fields are empty until hydration.
  // First arg is the client (undefined = read from CStarLibraryProvider);
  // second arg is the list params.
  const articles = new ArticlesState(undefined, { categorySlug: 'billing' });
</script>

<!-- Renders a static loading state on the server, real data after hydration -->
{#if articles.isLoading}
  <p>Loading…</p>
{:else}
  {#each articles.articles as a (a.id)}
    <a href="/help/{a.slug}">{a.title}</a>
  {/each}
{/if}

Prerendering with real data

If you want the help center to render with content during SSR (for SEO), fetch in a +page.server.js with the JS client and pass it through props. The state class on the client picks up where the server left off.

src/routes/help/+page.server.js
import { LibraryClient } from '@cstar.help/js/library';

export const prerender = true;

export async function load() {
  const library = new LibraryClient({ teamSlug: 'acme' });
  const articles = await library.articles({ limit: 20 });
  return { articles };
}

Next up