Add WebSockets to SvelteKit
To add WebSockets to a SvelteKit app, install svelte-ws, put one of its adapters in vite.config.ts, and export stream() and mutate() from a *.socket.ts file. The
dev server serves the WebSocket endpoint itself, so there is no second process to run and
nothing to add to hooks.server.ts. It needs SvelteKit 3.
Set up with the Svelte CLI
In an existing SvelteKit 3 project, one command does the setup below:
npx sv add svelte-ws # or: bunx sv add svelte-ws / pnpm dlx sv add svelte-ws It asks which runtime will serve the app and whether to add a demo page, then:
- adds
svelte-wstodependencies; - points the adapter in
vite.config.tsatsvelte-ws/adapter-nodeorsvelte-ws/adapter-bunand removes the old adapter package. Moving from@sveltejs/adapter-nodeor@sveltejs/adapter-bunkeeps the adapter’s options; from any other adapter it starts from the defaults; - with Bun, changes the
buildscript tobun --bun vite build(see Run it); - with the demo, adds a message board at
src/routes/socketthat every open tab shares throughpublishandsubscribe, plusvalibotto validate what gets posted.
To skip the prompts, pass the answers inline: npx sv add svelte-ws=adapter:bun+demo:no. The sections below are the same setup done by
hand.
Install
bun add svelte-ws # or: npm i svelte-ws / pnpm add svelte-ws The package brings the definers, the pub/sub bus, the Vite plugin and both adapters. Its
own dependencies are two small ones: ws (the Node transport) and the Standard Schema
types. No broker or extra service is needed.
Wire up Vite
Pick a svelte-ws adapter in vite.config.ts:
import { defineConfig } from 'vite';
import { sveltekit } from '@sveltejs/kit/vite';
import adapter from 'svelte-ws/adapter-node'; // or 'svelte-ws/adapter-bun'
export default defineConfig({
plugins: [
sveltekit({
adapter: adapter()
})
]
}); That’s the whole wiring: the adapter brings its own Vite plugin, and there are no hooks.server.ts changes and no special dev runtime. In vite dev the plugin serves the
WebSocket endpoint on the dev server itself, same origin, and in production the adapter
serves it. Both adapters are SvelteKit’s official ones with the WebSocket added. adapter-node goes through the ws package, adapter-bun through native Bun WebSockets. Your application code is the same
either way; see adapters for how to choose.
Write your first socket module
Create a *.socket.ts file next to the components that use it:
// src/routes/ticker/ticker.socket.ts
import { stream } from 'svelte-ws';
export const clock = stream(async function* (_args, ctx) {
while (!ctx.signal.aborted) {
yield new Date().toISOString();
await new Promise((resolve) => setTimeout(resolve, 1000));
}
}); Use it from a component:
<script>
import { clock } from './ticker.socket';
const time = clock();
</script>
<time>{time.current ?? '…'}</time> Every subscriber with the same arguments shares one server stream, and the stream is released when the last component using it is destroyed.
Run it
vite dev serves the WebSocket endpoint on the dev server, whichever adapter you picked.
The production build differs: adapter-bun calls Bun’s own build API, so vite build has
to run inside Bun. Put the --bun flag in the script once and bun run build works as
usual. In package.json, with adapter-bun:
"scripts": {
"dev": "vite dev",
"build": "bun --bun vite build",
"start": "bun ./build"
} With adapter-node:
"scripts": {
"dev": "vite dev",
"build": "vite build",
"start": "node build"
} Without the flag, bun run build starts Vite under Node (its CLI declares #!/usr/bin/env node) and adapter-bun stops with “adapter-bun requires running the
SvelteKit build with Bun”. Only the build needs Bun; vite dev works either way.
Next steps
- Streams & mutations: the full authoring API
- The pub/sub bus: fan-out between transports
- socket.config.ts: startup code, auth at upgrade, limits
- Testing: drive socket modules in unit tests without a server