すべての記事
ViteHMRlocal developmentlocalhost tunneling

Vite + トンネル: 本当に動くHMR

Viteの開発サーバーをトンネルするのはうまくいきます — 最初につまずく2つのことを除けば。ページは読み込まれるのにHMRが効かない。あるいはViteがトンネルのホスト名を拒否してページがまったく読み込まれない。どちらも、どこを見ればいいか分かれば設定1行で解決します。

問題1: 「Blocked request. This host is not allowed.」

Vite 5以降はデフォルトでhost-check保護が追加されました。トンネルURLを開いてBlocked request. This host is not allowedと表示されたら、それはこのチェックがあなたの*.portpreview.devホスト名を拒否しています。

vite.config.tsでの修正:

export default defineConfig({
  server: {
    host: true,                            // listen on 0.0.0.0
    allowedHosts: ['.portpreview.dev'],   // accept any subdomain
  },
});

先頭のドットでサフィックス一致になります。厳格にしたいなら特定のトンネルホスト名を入れてもよいですが、日々の開発ではサフィックスが便利です — トンネルセッションのURLは入れ替わります。

問題2: HMRが読み込まれた後、静かに死ぬ

HMRはWebSocket上で動きます。Viteがlocalhost:5173で配信し、ブラウザはhttps://abc123.portpreview.devから読み込むと、HMRクライアントはサーバーが伝えたURL — ローカルのもの — に接続しようとします。トンネルはそれを転送しないので、WebSocketは失敗し、HMRは静かに止まります。

HMR設定を修正します:

export default defineConfig({
  server: {
    host: true,
    allowedHosts: ['.portpreview.dev'],
    hmr: {
      clientPort: 443,            // browser connects on 443 (HTTPS)
      protocol: 'wss',            // secure WebSocket through the tunnel
    },
  },
});

これでHMRクライアントはwss://abc123.portpreview.dev(ポート443)に接続し、トンネルがアップグレードを転送して、編集が期待どおり反映されます。静的リロードだけで十分でトンネル越しのHMRが不要なら、これは飛ばせます — ただしcmd-Rを多用することになります。

そもそもなぜViteをトンネルしたいのか

本当の理由は3つ:

  • モバイルテスト。 URLをスマホで開けば、HMR付きで同じビルドが見えます。画面共有やデバイス別エミュレータより優れています。完全なパターンはトンネルでのモバイルテストを参照。
  • 進行中の作業の共有。 デザイナーやPMにリンクを送れば、あなたのブランチをライブで見られます。デプロイ手順は不要です。
  • HTTPSが必要なOAuthやWebhookのフロー。 Stripe(リダイレクトのみ)やAuth0などと話すVite配信のフロントエンド。選び方はmkcert対トンネルで扱っています。

SvelteKit、Astro、SolidStartはすべてViteを使う

上記の設定は、開発サーバーとしてViteを出荷するあらゆるフレームワークに当てはまります。SvelteKitのvite.config.jsも同じ形です。Astroはviteサブオブジェクトを持つ独自のastro.config.mjsがあります — そこに同じserverオプションを置きます。SolidStart、Nuxt 3(Vite経由)、Qwik Cityも同じパターンです。

本番ビルドは別の話

上記はすべて開発サーバーの設定です。vite previewを実行したりビルド済みバンドルを配信したりするときは、HMRもhost-checkミドルウェアもないため、何も関係ありません。その時点でトンネルは静的ファイルを転送しているだけです。

小さなこといくつか

  • 厳格なhost CORS。 フロントエンドが別オリジン(別のlocalhostポート、または別のAPIトンネル)のAPIを叩くなら、API側でCORSを設定します。Viteはserver.proxyで指示しない限りデフォルトではプロキシしません。
  • WebSocketを多用するアプリ。 Vite HMRはWebSocketが1つです。アプリがゲーム状態、チャット、ライブデータ用に別のWebSocketを使うなら、それは別物で、クライアントコードでもトンネルURLを使うように設定する必要があります。
  • env ファイルのトンネルURL。 フロントエンドが自分の公開アドレスを知る必要があるとき、アクティブなトンネルURLを.env.localVITE_PUBLIC_URLとして入れます。import.meta.env.VITE_PUBLIC_URLでクライアントから読みます。

手順

  1. server.hostallowedHostshmrブロックをVite設定に追加します。
  2. 開発サーバーを再起動します。
  3. npx portpreview 5173(またはViteが使うポート)を実行します。
  4. ブラウザかスマホでHTTPS URLを開きます。
  5. コンポーネントを編集します。HMRがフルリロードなしでページを更新することを確認します。

HMRが更新しないなら、ブラウザのコンソールを開きましょう — ViteはWebSocket接続の試行をログに出し、試したURLが見えます。それでhmr.clientPort/protocolの変更が効いたか分かります。

WebSocketアップグレードをデフォルトで保持するトンネルのために、PortPreviewのウェイトリストに登録してください。

よくある質問

なぜViteは「host is not allowed」でトンネルURLをブロックするのですか?
Vite 5以降は、未知のホスト名をデフォルトで拒否するhost-checkを追加しました。トンネルのリクエストを受け入れるには、allowedHosts: ['.portpreview.dev'](またはトンネルのドメイン)をserver設定に追加してください。
トンネル越しにVite HMRを動かすには?
vite.config.tsでhmr.clientPort: 443とhmr.protocol: 'wss'を設定します。これにより、HMRクライアントはlocalhostへ直接到達しようとせず(マシン外からは失敗する)、トンネルのHTTPS/WSS経由で接続します。
これはSvelteKit、Astro、Nuxtでも動きますか?
はい。Viteを開発サーバーとして使うフレームワークはどれも、同じserver.host、allowedHosts、hmr設定を取ります。Astroはastro.config.mjsのvite配下にネストし、ほかは通常のVite設定ファイルに置きます。