Skip to content

Vue

DurableWS ships a first-class Vue 3 composable in the box: durablews/vue. No extra package — Vue is an optional peer dependency, so installing durablews without Vue never warns, and the composable only loads when you import it.

Terminal window
npm install durablews

Pass a config and the composable owns the client: it connects immediately and closes the connection when the component is unmounted.

<script setup lang="ts">
import { useWebSocket } from "durablews/vue";
const { state, lastMessage, send } = useWebSocket({
url: "wss://example.com/socket"
});
</script>
<template>
<p>Connection: {{ state }}</p>
<p>Last message: {{ lastMessage }}</p>
<button @click="send({ type: 'hello' })">Say hello</button>
</template>

Everything durable-by-default applies: the connection reconnects with full-jitter backoff, send() queues while disconnected and flushes on open, and state walks through reconnecting so your UI can show it.

PropertyTypeWhat it is
stateComputedRef<ConnectionState>idle → connecting → open → reconnecting → …
lastMessageShallowRef<TIn | undefined>The latest decoded (and validated) inbound message
lastErrorComputedRef<Event | Error | null>The most recent failure, if any
retryAttemptComputedRef<number>Retries used in the current disconnection episode
queueLengthComputedRef<number>Outbound messages waiting for an open socket
send / connect / closefunctionsProxies to the client
clientWebSocketClientThe full client, for everything else (on(), use(), …)

lastMessage keeps only the latest message — DurableWS never accumulates message history. To process every message, handle the event on the client:

const { client } = useWebSocket({ url });
client.on("message", (msg) => {
// every message, not just the latest
});

Pass a Standard Schema (zod, valibot, arktype, …) and lastMessage is fully typed — plus every inbound message is validated at runtime:

<script setup lang="ts">
import { useWebSocket } from "durablews/vue";
import { z } from "zod";
const Message = z.object({ type: z.string(), body: z.string() });
const { lastMessage } = useWebSocket({
url: "wss://example.com/socket",
schema: Message
});
// lastMessage: ShallowRef<{ type: string; body: string } | undefined>
</script>

Pass an existing client instead of a config and the composable only observes it — it never connects or closes a client it was handed. This is the pattern for an app-wide connection used by many components:

// src/ws.ts — the app owns this client
import { defineClient } from "durablews";
export const ws = defineClient({ url: "wss://example.com/socket" });
ws.connect();
<script setup lang="ts">
import { useWebSocket } from "durablews/vue";
import { ws } from "@/ws";
// Reactive views over the shared connection; unmounting this
// component does not close it.
const { state, lastMessage } = useWebSocket(ws);
</script>

The composable is SSR-safe: when no global WebSocket exists (the server render), auto-connect is skipped and the client connects when setup runs again in the browser.