Skip to content

fix: support WebSocket upgrades in Vite dev server#3737

Closed
bartlomieju wants to merge 2 commits into
mainfrom
fix/websocket-upgrade-dev-server
Closed

fix: support WebSocket upgrades in Vite dev server#3737
bartlomieju wants to merge 2 commits into
mainfrom
fix/websocket-upgrade-dev-server

Conversation

@bartlomieju

Copy link
Copy Markdown
Contributor

Summary

  • Fix WebSocket upgrade requests never reaching Fresh handlers in Vite dev mode (Fresh V2 Beta WebSocket upgrade request never reaches server behind vite development environment #3350)
  • Vite's Connect-based server fires upgrade events on the underlying http.Server rather than routing them through middleware, so Deno.upgradeWebSocket() was never called
  • Start a Deno.serve() on a random port alongside Vite and proxy upgrade requests to it via raw TCP socket forwarding, where Deno.upgradeWebSocket() works natively
  • Vite's own HMR WebSocket paths (/__vite_hmr, /__vite_ping) are excluded from proxying

Test plan

  • New test vite dev - websocket upgrade verifies echo via WebSocket in dev mode
  • Verify existing dev server tests still pass
  • Manual test with a real WebSocket-based app in dev mode

Fixes #3350

🤖 Generated with Claude Code

WebSocket upgrade requests never reached Fresh handlers in dev mode
because Vite's Connect-based server fires 'upgrade' events on the
underlying http.Server rather than routing them through middleware.

Fix by starting a Deno HTTP server on a random port alongside Vite
and proxying upgrade requests to it via raw TCP socket forwarding.
This allows Deno.upgradeWebSocket() to work natively.

Fixes #3350

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bartlomieju bartlomieju force-pushed the fix/websocket-upgrade-dev-server branch from 8bc3c1c to 1ac26e1 Compare March 29, 2026 08:33
- Replace Buffer type with Uint8Array to fix CI type checking
- Add onError handler to Deno.serve to suppress broken pipe errors
- Add close event handlers on proxy sockets for clean teardown
- Wrap destroy calls in try/catch to prevent cascading errors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@bartlomieju

Copy link
Copy Markdown
Contributor Author

The second Deno.serve() + TCP proxy approach here works but is heavier than ideal. The root cause is that Deno.upgradeWebSocket() doesn't work with requests from node:http upgrade events — filed denoland/deno#33220 to track fixing this at the Deno level.

Once that Deno issue is resolved, this workaround can be replaced with a simple upgrade event handler that calls the Fresh handler directly. Until then, this is the only viable approach.

@fibibot fibibot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strategy is sensible — Vite's Connect-based server can't run Deno.upgradeWebSocket(), so proxying upgrade frames through a separate Deno.serve is the right shape.

Blocker: CI is red on 53bc5c82 — nine vite dev tests timed out at ~55s each (loads islands, tailwind no _app, tailwind _app, css modules, route css import, partial island, nested islands, inline env vars, can import json in npm package), all going through Puppeteer + SSR. The run is from 2026-03-29; a rebase + re-run will at least rule out drift from main, then it's worth confirming whether the new Deno.serve or the upgrade handler is interacting with the browser sessions.

  • nit: connect(wsPort, "127.0.0.1", ...) reads wsPort synchronously inside the upgrade callback, but it's only set in the async onListen. If an upgrade fires before listening completes, connect(0, ...) fails. Practically rare (the Vite server hasn't started accepting either) but easy to harden with a Promise.withResolvers() gate.
  • nit: req.url === \"/__vite_hmr\" is an exact-string match. Any query string would miss the bypass and proxy to the Deno server instead of letting Vite handle HMR. req.url?.startsWith(\"/__vite_hmr\") is safer.

@bartlomieju

Copy link
Copy Markdown
Contributor Author

Closing in favor of #3823

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fresh V2 Beta WebSocket upgrade request never reaches server behind vite development environment

2 participants