FeaturesGuidesBlogDocumentationFreeGet Started

Vite HMR Through a Tunnel (Config Checklist)

Copy-paste vite.config.js settings for allowedHosts and HMR over HTTPS — pair with the deep-dive blog on why HMR breaks.

Deep dive: For symptoms, WebSocket theory, and troubleshooting, read Vite HMR Over an HTTPS Tunnel. This guide is the short configuration checklist.

Why Vite HMR breaks through tunnels

Vite's dev server uses WebSockets for Hot Module Replacement (HMR). When you access your app through a tunnel URL, two things differ from normal localhost development:

  1. Host header — the browser sends k7m2x9p4q1.localtolink.com, not localhost
  2. WebSocket connection — HMR must connect via wss:// on port 443 (the tunnel terminates TLS)

Without configuration, Vite may block the foreign hostname or attempt HMR on the wrong port/protocol. The fix is a few lines in vite.config.js.

Prerequisites

Step 1: Start Vite and the tunnel

Terminal 1:

npm run dev

Terminal 2:

npx localtolink --port 5173

Note the public URL, e.g. https://k7m2x9p4q1.localtolink.com.

Step 2: Configure Vite

Add to vite.config.js or vite.config.ts:

import { defineConfig } from "vite";

export default defineConfig({
  server: {
    allowedHosts: [".localtolink.com"],
    hmr: {
      clientPort: 443,
      protocol: "wss",
    },
  },
});

Restart Vite after saving — config is read at startup.

What each setting does

SettingPurpose
allowedHosts: [".localtolink.com"]Allows the dev server to respond to tunnel hostnames (Vite 5.4+ host check)
hmr.clientPort: 443HMR WebSocket connects via HTTPS port — tunnel terminates TLS at edge
hmr.protocol: "wss"Secure WebSocket through the tunnel

The leading dot in .localtolink.com matches all subdomains, so you do not update config when LocalToLink assigns a new random subdomain.

Step 3: Verify HMR works

  1. Open the tunnel URL in a browser
  2. Continue through any safety prompt if shown (first visit only)
  3. Edit a component file — e.g. change button text
  4. Confirm the UI updates without a full page refresh
  5. Check browser DevTools → Network → WS tab for an active HMR WebSocket

If you see a full page reload instead of hot update, HMR is falling back — check the troubleshooting section.

Phone testing with QR code

LocalToLink prints a QR code in the terminal. Scan it to test HMR on a physical device:

npx localtolink --port 5173
# QR code prints automatically; use --no-qr to hide

Mobile HMR confirms touch interactions and responsive layout with real hot reload. See Test Localhost on Your Phone.

API proxy considerations

If your Vite config proxies API requests to a backend:

export default defineConfig({
  server: {
    allowedHosts: [".localtolink.com"],
    hmr: { clientPort: 443, protocol: "wss" },
    proxy: {
      "/api": "http://localhost:8080",
    },
  },
});

The proxy runs server-side on your machine — it works through the tunnel because Vite handles the proxy locally before responding to the browser.

WebSocket support

LocalToLink forwards WebSocket upgrades bidirectionally. Your local Vite server handles the HMR protocol — the tunnel transports frames transparently. No extra configuration is needed on the tunnel side.

This also applies to custom WebSocket endpoints in your app, not just HMR. See WebSocket Through a Tunnel.

Browser interstitial

When the visitor interstitial policy is enabled, the first browser visit to a new subdomain may show LocalToLink's Opening a shared link page — tap Continue once (24-hour cookie per subdomain). WebSocket and HMR traffic from dev tooling often behaves like non-browser or already-bypassed traffic; if HMR stalls, open the tunnel URL in a tab and continue through the prompt, or send localtolink-skip-browser-warning in automated tests.

Session limits

LimitImpact on HMR
2 hoursRestart tunnel and refresh browser when session expires
1 GB bandwidthSufficient for development HMR
10 req/sHMR updates are low volume — no issue
20 concurrent connectionsFine for solo or small team testing

Troubleshooting

IssueFix
HMR WebSocket connection failedVerify clientPort: 443 and protocol: "wss"
Blocked host errorAdd .localtolink.com to allowedHosts
Full page reload onlyCheck browser console for WS errors; restart Vite
HMR works on localhost, not tunnelOpen app via tunnel URL, not localhost
WS connects then disconnectsTunnel session may have expired — restart both
Mixed content warningsEnsure you use https:// tunnel URL, not http://

Alternative: preview mode

For demos where HMR is not needed, build and preview:

npm run build
npm run preview
npx localtolink --port 4173

Vite preview serves static production assets — no HMR, but no WebSocket config needed.

Framework-specific notes

React + Vite: HMR config above applies directly.

Vue + Vite: Same config. Vue SFC hot reload uses the same HMR WebSocket.

Svelte + Vite: Same config via @sveltejs/vite-plugin-svelte.

Next.js users: Next.js is not Vite-based — see Next.js Dev Server Through a Tunnel.

FAQ

Does this work with Vite 6?
Yes. allowedHosts and hmr settings are stable across recent Vite versions.

Can I use a custom HMR path?
If you changed Vite's default HMR path, ensure your tunnel forwards the path correctly. Default config works without changes.

Why port 443 for HMR?
The browser connects to the tunnel's HTTPS endpoint on port 443. The tunnel forwards WebSocket frames to your local Vite server on 5173.

See Expose Localhost to the Internet for tunnel basics.

Try it now

Start a tunnel from your project directory — no account required.

$ npx --yes localtolink@latest

See the full CLI reference for flags and exit codes.

LocalToLink CLI 1.0.1 — verified 2026-09-20