Vite
Add a shared live feed to an existing Vite app. Choose your renderer and data library below for a complete recipe. The walkthrough on this page uses React.
You need an API that supplies a queue value such as { "open": 12 }. Choose its
transport in step 3 and replace the URL and type with your own. If your
API runs on another local port, use your app’s existing dev proxy or an absolute
URL with CORS configured on the API.
Choose your stack
Section titled “Choose your stack”Use a complete recipe for your renderer and data library, or continue below for the basic direct subscription.
1. Add the plugin
Section titled “1. Add the plugin”pnpm add spinetabAdd spinetab() alongside the plugins already in your config:
import react from "@vitejs/plugin-react";import { spinetab } from "spinetab/vite";import { defineConfig } from "vite";
export default defineConfig({ plugins: [react(), spinetab()],});The plugin builds the worker and includes the adapters your app imports. Restart the dev server after changing the config.
2. Create the client
Section titled “2. Create the client”Create one client for your app and bind the React hooks to it:
import { createSpinetab } from "spinetab";import { bindClient } from "spinetab/react";
export const spinetab = createSpinetab();export const { useLive, useSpinetabStatus } = bindClient(spinetab);Import these hooks wherever you need them. No provider is required.
3. Choose your source
Section titled “3. Choose your source”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.
import { polling } from "spinetab/polling";
export const queueSource = polling<{ open: number }>("/api/queue");Your endpoint serves text/event-stream with JSON in each data: field.
import { sse } from "spinetab/sse";
export const queueSource = sse<{ open: number }>("/api/queue/events");Your WebSocket endpoint sends a JSON queue value in each text frame. This is a raw WebSocket feed; GraphQL and Socket.IO need their own adapters.
import { websocket } from "spinetab/websocket";
export const queueSource = websocket<{ open: number }>("/ws/queue", { decoder: "json",});Your endpoint streams one JSON value per line (NDJSON). Only declare it repeatable when opening the request again is safe.
import { stream } from "spinetab/stream";
export const queueSource = stream<{ open: number }>("/api/queue/stream", { repeatable: true,});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.
4. Render a subscription
Section titled “4. Render a subscription”import { queueSource } from "./queue-source";import { useLive } from "./live";
export function Queue() { const { data, error } = useLive(queueSource, { reconcile: "latest", });
if (error) return <p role="alert">Could not load the queue: {error.code}</p>; if (data === undefined) return <p role="status">Loading queue…</p>;
return <p>{data.open} open</p>;}Render it in your app:
import { Queue } from "./Queue";
export default function App() { return <Queue />;}The hook subscribes on mount and cleans up on unmount. Polling reads every five seconds by default and pauses when no consumer is eligible, for example when all subscribing tabs are hidden. It performs a catch-up read when a tab returns.
5. Check it across tabs
Section titled “5. Check it across tabs”Open the app in two tabs of the same browser profile. With SharedWorker available, matching subscriptions share the upstream feed or polling schedule. Each tab still renders its own component and holds its own value.
To inspect the mode, temporarily render this component alongside Queue:
import { useSpinetabStatus } from "./live";
export function LiveStatus() { const status = useSpinetabStatus(); return <p>Mode: {status.mode}</p>;}shared means the client is using a worker. local means this tab runs its own
subscriptions. See execution modes for fallback reasons.
Server logs are the useful check for shared upstream work. With polling, a new
consumer may trigger a fresh read without creating another ongoing schedule.
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.
Next steps
Section titled “Next steps”- Use polling options to change the interval, or choose another subscription source.
- Keep your existing Apollo, TanStack Query or SWR data flow through its integration.
- Add authentication and recovery handling for your app’s feeds.
For plugin options, base paths and custom builds, see bundler configuration.