# vue-native-websocket: one shared socket for a whole Vue 3 app

> vue-native-websocket wraps the browser's own WebSocket in a Vue 3 plugin and a composable, so an application shares a single connection, reads its state as refs, and receives events through hooks instead of committing straight into a store. Version 3 dropped Vue 2 and automatic Vuex writes, which is the main decision to review before adopting it.

**nathantsoi/vue-native-websocket** — native websocket with vuex integration

- Repository: https://github.com/nathantsoi/vue-native-websocket
- Stars: 935 · Forks: 154
- Language: TypeScript
- License: not declared
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/nathantsoi-vue-native-websocket

## One client for the whole application

The description is short, native WebSocket with Vuex integration, and version 3 has moved away from that framing considerably. What the package actually provides is a thin layer over the browser's built-in WebSocket object, installed as a Vue plugin and consumed through a composable. The central design decision is that there is one shared client. `useSocket()` returns that client, so a component that calls it is not opening a second connection, it is reading the same one. That matters for a chat interface, a live dashboard or a notifications channel, where a component-per-connection design multiplies sockets the browser then has to multiplex or, more often, hits the per-domain connection limit. The peer dependency in `package.json` pins Vue at `^3.5.0`, so the package targets current Vue 3 releases rather than the wider 3.x range. The repository also declares `sideEffects: false`, which tells bundlers they can drop unused imports, a small but real detail for a library that applications import in many places.

## useSocket exposes refs, not a socket instance

The composable hands back a set of reactive references rather than the raw socket. The documented shape destructures into `socket`, `status`, `lastMessage`, `lastJsonMessage`, `error` and `reconnectAttempt`. Connection state is a single string ref with a fixed set of values:

```ts
'idle' | 'connecting' | 'open' | 'closing' | 'closed' | 'reconnecting' | 'error'
```

That list is worth reading carefully, because it encodes the whole lifecycle in one place, including a distinct `reconnecting` state that lets a UI show a reconnect banner rather than a hard failure. The derived values, `lastMessage` for the raw event data and `lastJsonMessage` for the parsed form, cover the common case of a server that sends JSON. The README's component example shows the intended consumption pattern, where the button's disabled state is driven by comparing the status ref to `open`:

```vue
<script setup lang="ts">
import { computed } from 'vue'
import { useSocket } from 'vue-native-websocket'

const socket = useSocket()
const isConnected = computed(() => socket.status.value === 'open')

function sendPing () {
  socket.sendJson({ type: 'ping' })
}
</script>
```

The send helpers are on the client itself, so a component sends and receives through the same object it reads state from.

## Hooks replaced the automatic Vuex commits

The most significant change in version 3 is architectural. The README states plainly that the library no longer commits directly to Vuex, and that hooks are the way to connect the socket to Pinia, Vuex or any other store. Hooks can be configured at install time, with `onOpen`, `onMessage`, `onClose` and `onError`:

```ts
app.use(createSocketPlugin({
  url: 'ws://localhost:9090',
  onMessage: (event, client, json) => {
    console.info('message', event.data, json)
  }
}))
```

The `onMessage` signature is worth noting because it explains how JSON is handled: the hook receives the raw event, the client and a parsed JSON value. Hooks can also be registered later from a component, and each registration returns an unsubscribe function that pairs naturally with `onUnmounted`, which is how a component that only cares about messages while it is alive avoids leaking listeners:

```ts
const unsubscribe = socket.onMessage((event, client, json) => {
  console.log(event.data, json)
})

onUnmounted(unsubscribe)
```

The store examples in the README follow the same pattern, with an `onOpen` handler that flips a connected flag in a Pinia store and an `onMessage` handler that pushes `json ?? event.data` into it.

## Reconnect is opt-in with a bounded number of attempts

Automatic reconnection is disabled by default and enabled through plugin options:

```ts
app.use(createSocketPlugin({
  url: 'ws://localhost:9090',
  reconnect: true,
  reconnectAttempts: 5,
  reconnectDelay: 3000,
  onReconnect: attempt => {
    console.info('reconnecting', attempt)
  },
  onReconnectError: attempt => {
    console.error('reconnect failed after attempt', attempt)
  }
}))
```

Three details matter for production use. The number of attempts is bounded, so a server that is down for longer than five attempts leaves the client in its error path with the `onReconnectError` hook firing rather than retrying forever. The delay is a single configured value rather than a documented exponential backoff, and the README does not describe any backoff strategy, jitter or attempt ceiling behaviour beyond the attempt counter. And the reconnect attempt count is exposed as the `reconnectAttempt` ref, so a UI can show how many retries have happened. Legacy option names, `protocol`, `reconnection`, `reconnectionAttempts` and `reconnectionDelay`, are still accepted as aliases, which eases migration from the 2.x line at the cost of two naming schemes coexisting in the same option bag.

## Installing the package and wiring the plugin

Installation is a single package manager call, with npm and yarn both documented:

```bash
npm install vue-native-websocket
```

```bash
yarn add vue-native-websocket
```

The plugin is then registered on the app instance, and the URL is where the socket will connect:

```ts
import { createApp } from 'vue'
import App from './App.vue'
import { createSocketPlugin } from 'vue-native-websocket'

createApp(App)
  .use(createSocketPlugin({
    url: 'ws://localhost:9090'
  }))
  .mount('#app')
```

If the connection should not start on install, `connectManually: true` defers it, and the client exposes `connect()`, `connect(url)` with an alternative endpoint, and `disconnect()`. For Options API components the plugin also installs global properties, `this.$connect()`, `this.$send('hello')`, `this.$sendJson({ hello: 'world' })` and `this.$disconnect()`, so a codebase that has not migrated to the Composition API can still drive the socket, though the v3 notes say Vue 2 itself is no longer supported.

## The v3 break list decides the migration cost

The README publishes a short breaking-changes list that is really the adoption guide. Vue 2 is no longer supported. `Vue.use(...)` is replaced by `app.use(createSocketPlugin(...))`. Vuex automatic commit and dispatch handling has been removed, which is the change that touches the most application code. Dynamic `this.$options.sockets` listeners have been removed. And TypeScript is now the source of truth, with generated declarations included in the package. That last point is visible in `package.json`, where the build is `vite build && tsc -p tsconfig.build.json`, the typecheck script is `tsc --noEmit`, and the published outputs are ESM, CJS, UMD and declaration files, with the `exports` map pointing `import` at the `.mjs` build and `require` at the `.cjs` build. On licences, the README states MIT and the `package.json` declares the MIT identifier as its licence value, while the repository licence field is empty, so the package metadata is the more precise signal. Release history shows the scale of the jump, with v2.0.15 in August 2024 and v3.0.0 in June 2026, and the last push on 2026-10-16.

## Where it stops being the right tool

Three boundaries are worth naming. The first is scope: this is a browser package built on the native WebSocket object, and the README does not describe a Node or worker server client, so a server-side consumer is out of scope. The second is protocol work. The library moves bytes and hands you JSON, but there is no message envelope, no acknowledgement layer, no heartbeat or ping scheduling on the client side, and no reconnection for the manual `connect()` path beyond the plugin options. If your backend needs request-response correlation or exactly-once semantics, you are writing that layer yourself. The third is the size of the dependency surface: `package.json` requires Node `^20.19.0 || >=22.12.0` for the toolchain, and the dev stack is Vite, Vitest, ESLint and TypeScript, which is comfortable for a Vue 3 shop but means the build from source assumes a modern Node setup. Building and testing locally is a documented five-command sequence, and the example app under `examples/vue3-vite` connects to the public `wss://echo.websocket.org` endpoint, so a JSON payload sent there comes back into the event log.

## Conclusion

vue-native-websocket fits Vue 3 teams on Vue 3.5 or newer who want one shared socket, reactive connection state and reconnect logic without writing a composable, and who prefer to route messages into Pinia or Vuex through their own hooks. It does not fit Vue 2 applications, nor code that relied on the removed automatic store commits or on this.$options.sockets listeners. Before adopting it, check that your project is on Vue 3.5 or later, confirm the reconnect options suit your server's restart behaviour, and read the breaking changes list in the README before porting from the 2.x line.

## FAQ

### How do I install vue-native-websocket?

Run npm install vue-native-websocket, or yarn add vue-native-websocket. The package requires Vue 3.5 or newer as a peer dependency, and the README shows the plugin registered with app.use(createSocketPlugin({ url: 'ws://localhost:9090' })).

### Does vue-native-websocket still write to Vuex automatically?

No. The README states that the library no longer commits directly to Vuex. You connect the socket to Pinia, Vuex or any other store through the onOpen, onMessage, onClose and onError hooks.

### How do I reconnect automatically when the socket drops?

Pass reconnect: true to the plugin along with reconnectAttempts and reconnectDelay, and handle the onReconnect and onReconnectError hooks. The number of attempts is bounded, so a long outage stops retrying rather than reconnecting indefinitely.

## Sources

- [Issues](https://github.com/nathantsoi/vue-native-websocket/issues)
- [nathantsoi/vue-native-websocket on GitHub](https://github.com/nathantsoi/vue-native-websocket)
- [README](https://github.com/nathantsoi/vue-native-websocket/blob/master/README.md)
- [Releases](https://github.com/nathantsoi/vue-native-websocket/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/nathantsoi-vue-native-websocket
