websockets/ws · critical · Error
ws does not work in the browser. Browser clients must use…
Error message
ws does not work in the browser. Browser clients must use the native WebSocket object
What it means
The `ws` package's package.json sets "browser": "browser.js", so any bundler that honors the browser field (webpack, vite, rollup via browser-field plugin, esbuild with platform=browser) resolves `require('ws')`/`import ... from 'ws'` to this 8-line stub, which throws synchronously on import. `ws` relies on Node core modules (net, tls, crypto, stream, zlib) that do not exist in a browser, and browsers already ship a native `WebSocket`, so the library refuses to load rather than silently break. The throw happens at module-evaluation time, so it surfaces as a top-level crash of the bundle.
Solutions
- In browser code, use the global native `WebSocket` instead of importing `ws` (no import statement needed).
- Use an isomorphic wrapper such as `isomorphic-ws` that returns native `WebSocket` in the browser and `ws` on Node.
- Configure your bundler to alias `ws` to a browser polyfill (e.g. `websocket` package) or to `false`/an empty module in the browser build.
- Split client and server entry points so `ws` is only imported from the Node entry, never from code the bundler emits for the browser.
Example fix
// before
import WebSocket from 'ws';
const socket = new WebSocket('wss://example.com');
// after (browser entry — no import, use the global)
const socket = new WebSocket('wss://example.com'); Defensive patterns
Strategy: fallback
Validate before calling
// Detect a browser environment before importing ws.
const isBrowser =
typeof window !== 'undefined' && typeof window.document !== 'undefined';
if (isBrowser) {
// Use the native WebSocket; never import `ws` here.
} Type guard
// isomorphic getter: native WebSocket in browsers, ws on Node
function getWebSocketImpl() {
if (typeof window !== 'undefined' && typeof window.WebSocket !== 'undefined') {
return window.WebSocket;
}
// eslint-disable-next-line global-require
return require('ws');
} Prevention
- Keep `ws` imports out of any module that a browser bundler can reach; gate them behind a Node-only entry point.
- Configure webpack/vite/rollup to alias `ws` to a stub or to `false` in the browser build, or use `isomorphic-ws`.
- Add a lint rule or a bundler resolve check that fails the build if `ws` leaks into a browser chunk.
When it happens
Trigger: Building a web bundle that imports `ws` directly with target 'web' or platform 'browser'; an isomorphic/SSR app whose client entry pulls in `ws` instead of the native `WebSocket`; a dependency (e.g. a GraphQL/mqtt/stomp client) that internally `require('ws')` and gets bundled for the browser.
Common situations: Sharing the same networking module between a Node server and a browser client; migrating from Node to a browser target without swapping the WebSocket implementation; SSR frameworks (Next.js, Nuxt) that compile the same code for both runtimes; copy-pasted examples that `import WebSocket from 'ws'`.
AI-assisted analysis of websockets/ws@c791e707ea (2026-08-06).
Data as JSON: /api/errors/ec529b1f7329accb.
Report an issue: GitHub.
Appendix: source
Thrown at browser.js:4
'use strict';
module.exports = function () {
throw new Error(
'ws does not work in the browser. Browser clients must use the native ' +
'WebSocket object'
);
};
View on GitHub (pinned to c791e707ea)