Skip to content

SvelteKit

This guide adds a shared live feed to an existing SvelteKit app using Svelte 5. It keeps server rendering enabled and uses Spinetab’s Svelte store binding.

The examples display a queue value such as { "open": 12 }. Choose the source that matches your backend in step 3. Use an existing endpoint or replace the URL and type with your API. Spinetab does not create the endpoint.

Use a complete recipe for your renderer and data library, or continue below for the basic direct subscription.

Open recipe

Terminal window
pnpm add spinetab

Keep the SvelteKit plugin and add spinetab() alongside it:

vite.config.ts
import { sveltekit } from "@sveltejs/kit/vite";
import { spinetab } from "spinetab/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [sveltekit(), spinetab()],
});

The plugin generates the worker from the adapters your app imports. Restart the dev server after changing the config; keep your existing SvelteKit deployment adapter in svelte.config.js.

src/lib/live.ts
import { createSpinetab } from "spinetab";
import { bindClient } from "spinetab/svelte";
export const spinetab = createSpinetab();
export const { liveStore } = bindClient(spinetab);

Choose the format your server already serves. Save one of these as queue-source.ts alongside live.ts. Each example receives the complete queue value, { "open": 12 }, so the component below stays the same.

Read a JSON response from GET /api/queue every five seconds by default.

queue-source.ts
import { polling } from "spinetab/polling";
export const queueSource = polling<{ open: number }>("/api/queue");

Polling options

These URLs are examples, not routes created by the plugin. Replace them with your API. For GraphQL, Socket.IO or a client library, see how sources and integrations fit together.

Create a SvelteKit page at /queue:

src/routes/queue/+page.svelte
<script lang="ts">
import { queueSource } from "$lib/queue-source";
import { liveStore } from "$lib/live";
const queue = liveStore(queueSource, {
reconcile: "latest",
});
</script>
<h1>Queue</h1>
{#if $queue.error}
<p role="alert">Could not load the queue: {$queue.error.code}</p>
{:else if $queue.data === undefined}
<p role="status">Loading queue…</p>
{:else}
<p>{$queue.data.open} open</p>
{/if}

$queue subscribes through Svelte’s store contract. The store starts live work when it gains a browser subscriber and releases it when the last subscriber leaves. During server rendering it returns the inactive snapshot without connecting. You do not need ssr = false or an onMount wrapper.

Keep the store in the component as shown. For URLs that change with route parameters, follow the reactive source rules so the store tracks the new source. Do not start subscriptions from a server load function.

Visit /queue in your running app. Navigating away removes that page’s store subscriber; another tab on the page keeps its own subscription alive.

Open the page in two tabs of the same browser profile. With SharedWorker available, matching subscriptions share the upstream feed or polling schedule. Each component keeps its own value. For polling, a new subscriber can trigger a fresh read; check ongoing requests rather than expecting exactly one initial request.

Polling reads every five seconds by default and pauses when no consumer is eligible, such as when all subscribing tabs are hidden. It reads again when a tab returns. If worker sharing is unavailable, each tab runs its own subscriptions; see execution modes.

reconcile: "latest" fits these full-state feeds. After a delivery gap, the next value restores the displayed state. Use a refresh policy for feeds of incremental changes.

See Vite framework configuration for build options and Svelte bindings for store APIs.