fix(dev): make Vite dev server reachable from LAN browsers#764
Merged
Conversation
Three coupled issues prevented browsing the dev server from a second machine on the LAN. bin/dev-server.sh - Drop ``export VITE_HOST=localhost`` default. It overrode Django's ``_vite_dev_server_host()`` auto-derivation, so even with mDNS resolution working, django-vite rendered ``<script src="http://localhost:5173/...">`` and LAN browsers tried to load the bundle from their own loopback. frontend/vite.config.js - ``server.cors.origin``: Vite 6+ defaults to a regex matching only ``localhost`` / loopback origins. Cross-origin fetches from the Django dev server (``http://<host>:9810``) to the Vite dev server (``http://<host>:5173``) returned ``Vary: Origin`` without ``Access-Control-Allow-Origin`` and the browser blocked the scripts. Mirror ``allowedHosts`` into a port-agnostic CORS regex. - ``server.hmr.host``: with ``server.host: true``, Vite bakes ``localhost`` into the HMR client's ``directSocketHost`` / ``socketHost`` strings. LAN browsers then opened the HMR WebSocket against their own loopback and got ERR_CONNECTION_REFUSED. Set ``hmr.host`` to the same ``VITE_HOST`` / mDNS-mangled hostname Django uses for the ``<script src>`` so the embedded URL agrees with where the page actually loaded the module from. - Include the resolved host in ``allowedHosts`` so an explicit ``VITE_HOST=...`` override (not matching ``os.hostname()``) is also trusted by Vite's host-header check and the new CORS regex. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three coupled issues prevented browsing the dev server from a second
Mac on the LAN (with the host advertised via mDNS as e.g.
hooloovoo.local).None reproduce on the host itself, which made the symptoms misleading.
bin/dev-server.shDropped
export VITE_HOST=localhost. The line overrode the auto-derivationin
codex/settings/__init__.py:_vite_dev_server_host(), so django-viterendered
<script src="http://localhost:5173/...">regardless of mDNS,and any browser that wasn't on the host Mac tried to load the bundle
from its own loopback.
frontend/vite.config.jsserver.cors.origin— Vite 6+ defaultsserver.cors.originto aregex that only matches
localhost/127.0.0.1/[::1]. Browsershitting Django at
http://<host>:9810and then fetching fromhttp://<host>:5173gotVary: Originback but noAccess-Control-Allow-Origin, so the scripts were blocked. MirrorallowedHostsinto a port-agnostic CORS regex.server.hmr.host— Withserver.host: trueand no explicithmr.host, Vite bakeslocalhostinto the HMR client'sdirectSocketHostandsocketHoststrings. Remote browsers openedthe HMR WebSocket against their own loopback (
ERR_CONNECTION_REFUSED).Set
hmr.hostto the sameVITE_HOST/ mDNS-mangled hostname Djangouses for
<script src>so the embedded URL agrees with where the pageactually loaded the module from.
allowedHosts— Include the resolvedHMR_HOSTso an explicitVITE_HOST=...override (one that doesn't matchos.hostname()) isalso trusted by Vite's host-header check and the new CORS regex.
Vite still hardcodes
localhostinto one more string — the diagnosticserverHostvariable in@vite/client— but that's only used inside aconsole error message template when HMR fails, never for an actual fetch.
Influencing it would require pinning
server.hostto a single name,which loses loopback access from the host machine.
Test plan
./bin/dev-server.shandbun run dev(infrontend/) start cleanly; browsinghttp://<host>.local:9810/loads the app with HMR working.
script tags resolve, HMR WebSocket connects.
curl -s http://<host>.local:5173/static/@vite/client | grep -E 'serverHost|directSocketHost'shows
directSocketHost=<host>.local:5173/...(onlyserverHostshould still be
localhost, which is cosmetic per above).curl -sI -H "Origin: http://<host>.local:9810" http://<host>.local:5173/static/@vite/clientreturns an
Access-Control-Allow-Originheader.http://localhost:9810/) stillworks.
🤖 Generated with Claude Code