@checkgate/ssr
v0.1.22
Published
Server-side rendering & bootstrap helpers for Checkgate — evaluate flags on the server, embed the result in the HTML, and hydrate the client with zero flag flicker (Next.js, Remix, SvelteKit, any SSR framework).
Downloads
279
Maintainers
Readme
Without SSR, a client-side flag SDK shows default values until its stream
bootstraps — so the "old" UI paints first and snaps to the "new" one a moment
later. @checkgate/ssr removes that flash:
- On the server, evaluate the flags a page needs for the current user and build a tiny, serializable bootstrap payload.
- Embed it in the HTML (a single
<script>tag, XSS-safe). - On the client, render the first paint straight from that payload — byte for byte what the server rendered — then let the live SDK take over for updates.
Zero dependencies. Framework-agnostic. Runs anywhere fetch exists.
Install
npm install @checkgate/ssr @checkgate/edge @checkgate/web@checkgate/ssr builds and reads the payload; @checkgate/edge fetches the flag
snapshot on the server; @checkgate/web is the live client SDK (and supplies the
shared WASM evaluation engine both sides use, so server and client agree exactly).
The flow (Next.js App Router)
1. Server — build the payload
// lib/checkgate-server.ts
import { CheckgateEdge } from '@checkgate/edge'
import { buildBootstrap } from '@checkgate/ssr'
import { createWasmCore } from '@checkgate/web/core'
let edge: CheckgateEdge | null = null
export async function getBootstrap(userKey: string, keys: string[]) {
if (!edge) {
edge = new CheckgateEdge({
serverUrl: process.env.CHECKGATE_URL!,
sdkKey: process.env.CHECKGATE_SDK_KEY!,
core: await createWasmCore(),
ttlSeconds: 30,
})
}
await edge.ensureFresh()
// buildBootstrap re-loads the snapshot into a core and resolves `keys` for the
// user; pass the same snapshot the edge client is holding.
return buildBootstrap({
core: await createWasmCore(),
snapshot: edge.snapshotFlags(), // your app can also fetch /flags/snapshot directly
userKey,
keys,
})
}2. Embed it in the document
// app/layout.tsx
import { bootstrapScriptTag } from '@checkgate/ssr'
import { getBootstrap } from '@/lib/checkgate-server'
export default async function RootLayout({ children }) {
const userKey = /* cookie/session id */ 'anonymous'
const payload = await getBootstrap(userKey, ['new-homepage', 'checkout-color'])
return (
<html>
<head dangerouslySetInnerHTML={{ __html: bootstrapScriptTag(payload) }} />
<body>{children}</body>
</html>
)
}3. Hydrate on the client
'use client'
import { useEffect, useState } from 'react'
import { readBootstrap, BootstrapValues } from '@checkgate/ssr'
import { CheckgateWeb } from '@checkgate/web'
const bootstrap = readBootstrap() // the embedded payload
const initial = new BootstrapValues(bootstrap)
export function useFlag(key: string) {
// First paint uses the server-resolved value → no flicker.
const [enabled, setEnabled] = useState(() => initial.isEnabled(key))
useEffect(() => {
// Seed the live client from the same snapshot, then subscribe to updates.
const client = new CheckgateWeb({
serverUrl: process.env.NEXT_PUBLIC_CHECKGATE_URL!,
sdkKey: process.env.NEXT_PUBLIC_CHECKGATE_SDK_KEY!,
bootstrap, // ← live-ready without waiting for SSE
})
client.connect().then(() => setEnabled(client.isEnabled(key, bootstrap.userKey)))
return () => client.disconnect()
}, [key])
return enabled
}Remix / SvelteKit follow the same three steps — build the payload in the
loader / +page.server, inject the script into the document template, and read
it in a client component/store.
API
| Export | Side | Description |
|---|---|---|
| buildBootstrap({ core, snapshot, userKey, keys?, attributes?, includeSnapshot? }) | server | Evaluate flags for a user, return a serializable payload. |
| serializeBootstrap(payload) | server | JSON, escaped for safe inlining in <script>. |
| bootstrapScriptTag(payload, { varName?, nonce? }) | server | A ready-to-inject <script> tag (supports a CSP nonce). |
| readBootstrap(varName?, win?) | client | Read the embedded payload (or null). |
| new BootstrapValues(payload) | client | .isEnabled(key) / .getValue(key, default) / .getVariant(key) over the server-resolved values. |
Safety
- The payload is serialized with
<,>,&, and the U+2028/U+2029 line separators escaped, so a flag key or value containing</script>can't break out of the tag.bootstrapScriptTagaccepts anoncefor strict CSPs. BootstrapValuesis null-safe and version-checked: a missing or stale-schema payload degrades to defaults rather than throwing, so a bad embed never breaks a render.
Part of Checkgate — the self-hosted feature-flag platform.
