@dif.sh/svelte
v0.6.1
Published
Svelte 5 adapter for @dif.sh/sdk — SSR-safe assignment, a server load helper, and exposure stores.
Maintainers
Readme
@dif.sh/svelte
Svelte 5 + SvelteKit adapter for @dif.sh/sdk.
It does the SSR-safe parts for you:
- Server assignment:
difLoad()mints a stable anonymousdif_uidcookie, derives audience attributes from request headers, and assigns every registered experiment with the SDK's pure assigner (no exposure firing, no shared state). - No flicker: the client reuses the server's decision and the same
dif_uid, so the first client render matches SSR. - Client-only exposures: the
experiment()store fires exactly one exposure on the client, never on the server.
npm i @dif.sh/sdk @dif.sh/svelteUsage
src/routes/+layout.server.ts (assign on the server):
import "$lib/dif/generated/client"; // side effect: registers active experiments
import { difLoad } from "@dif.sh/svelte/server";
export const load = (event) => ({ dif: difLoad(event) });src/routes/+layout.svelte (init the SDK once and publish the data to context):
<script lang="ts">
import { setContext } from "svelte";
import { initDif, DIF_CONTEXT_KEY } from "@dif.sh/svelte";
import { events } from "$lib/dif/generated/events"; // cloud config + publishable key
let { data, children } = $props();
setContext(DIF_CONTEXT_KEY, data.dif);
initDif({
data: data.dif,
events, // publishable key + apiUrl, baked in by `dif build`
});
</script>
{@render children()}Connect the project once with dif connect --key dif_pk_live_… (or dif init
--key …; add --url for a self-hosted deployment). The key and cloud URL live
in dif/config.yaml and dif build bakes them into
dif/generated/events.ts, so there are no PUBLIC_DIF_PUBLISHABLE_KEY /
PUBLIC_DIF_CLOUD_URL env vars to wire up.
Any component (read an experiment as a store):
<script lang="ts">
import { experiment, track } from "@dif.sh/svelte";
const cta = experiment("insights-cta-copy", {
control: () => "Read more",
variant_a: () => "Get the full breakdown",
});
</script>
<a onclick={() => track("insights_cta_click")}>{$cta.value}</a>ISR note
On an ISR-cached route the server load doesn't re-run per visitor, so the
cached HTML is shared. There, experiment() falls back to assigning on the
client from the dif_uid cookie: the server renders the control branch and the
client swaps once after hydration. Don't server-assign on an ISR route unless you
also vary the cache key on the headers difLoad reads.
Preview & QA forcing
Anyone can force a variant by opening a ?_dif= link, with no code and no
devtools. initDif reads it automatically (both difLoad server-side and the
client):
# force one experiment (persists for the tab session, then auto-clears)
https://staging.example.com/insights/foo?_dif=insights-cta-copy=variant_a
# multiple at once
…?_dif=insights-cta-copy=variant_a,home-hero=control
# clear
…?_dif=off
# generate the exact link from the CLI
dif qa --force insights-cta-copy=variant_a --preview-url https://staging.example.com/insights/fooA small preview badge appears whenever a force is active (showing the
experiment → variant and a one-click clear). A forced assignment never fires
an exposure, so QA can't pollute results. The force is stored in a session
_dif cookie (survives navigation, clears on tab close) and the param is
stripped from the address bar. Works in production too. Pass
allowOverrides: false to initDif/difLoad to disable per-env, or
preview: false to hide the badge.
API
difLoad(event, opts?)→DifData: server load helper (@dif.sh/svelte/server).attributesFromHeaders(headers)→AttributeBag: default header mapping; override viaopts.deriveAttributes.initDif(opts): client init; call once in the root layout.opts.allowOverrides/opts.preview(default true).experiment(id, branches)→Readable<{ value, variant }>.track(metric, opts?): fire a metric event.DIF_CONTEXT_KEY: the context key forDifData.
