Nuxt3のドキュメントに従って生成しただけのプロジェクトに、$ npm run dev
で起動したNuxtのアプリケーションにブラウザでアクセスすると、そのブラウザが再読込を繰り返すという症状に遭遇しました。
原因は、私がWebSocketで通信できるようにポートを設定していないことでした。この問題にNuxtやDocker、Docker-Composeなどは直接の関係がありませんでした。ただし、Dockerを使わずにホストで直接Node.jsを動かしていたら、この問題には遭遇しなかったはずです。
本問題発生時の構成
Docker-Composeは、Docker-Composeのホスト(つまりLXC)の3000番ポートとNode.jsコンテナの3000番ポートをマッピングしています。ホストで起動しているブラウザから、LXCのIPアドレスを指定することで、Nuxtへアクセスします。
内容を大幅に省略ていますが、docker-compose.yamlの内容です。
version: "3.8"
services:
mycontainer:
ports:
- 3000:3000
解決までの道のり
NuxtをProductionモードで実行する
問題発生時、NuxtをDevelopmentモードで起動していました。
$ npm run dev
代わりにProductionモードで実行します。Nuxt3はProductionモードのときには、環境変数NUXT_HOSTを与えないと外部からのアクセスを受け付けてくれません。
$ npm run build $ NUXT_HOST=0.0.0.0 npm run start
Productionモードで起動すると、本問題が発生しないことが確認できました。
Developmentモードで発生するが、Productionモードで発生しないことから、HMR(Hot Module Replacing)が原因ではないかと疑いました。Developmentモードを使用したいので、ここからは再びDevelopmentモードで原因の解決を進めました。
HMRを無効化しても再読込される
HMRが原因と疑ったので、早速これを無効化することを考えました。nuxt.config.ts
でHMRを無効化を指定できます。
export default defineNuxtConfig({
vite: {
server: {
hmr: false
}
}
})
残念ながら、これは解決に繋がりませんでした。
LXCの外に出る
HMRを疑いつつも確信がないので、他も疑ってかかります。
ホストで直接Podmanを動かしました。PodmanはDockerと互換性のあるコンテナエンジンです。標準でルートレスで使える点が気に入っています。
これを使い、LXCを省きました。
$ podman run --rm -it -p 3000:3000 node bash $ apt update $ apt install -y git $ npx nuxi init nuxt3-app $ cd nuxt3-app $ npm install $ npm run dev
ブラウザからhttp://localhost:3000
とアクセスします。残念ながら、これでも問題の解決には至りませんでした。
本記事の冒頭でWebSocketのためにポートを設定していないことが原因だったと書いていることから、察しの良い人は気がつくかもしれません。もしこここでhttp://{コンテナのIPアドレス}:3000
とアクセスしていたら、再読込が繰り返される症状は発生しなかったはずです。
ブラウザのコンソールを永続化する
私が使っているブラウザはFirefoxです。
ブラウザのコンソールに有益な情報が何も出ていないと思っていたのですが、ログの永続化をしたところ以下のログが表示されていることが分かりました。
Firefox can’t establish a connection to the server at ws://{LXCのIPアドレス}:24678/_nuxt/. [vite] server connection lost. polling for restart...
polling for restart...
といういかにもなログです。このログを出力している箇所をは以下の内容でした。
// ping server
socket.addEventListener('close', async ({ wasClean }) => {
if (wasClean)
return;
console.log(`[vite] server connection lost. polling for restart...`);
await waitForSuccessfulPing();
location.reload();
});
socket
のcloseイベントから実行される関数内にlocation.reload();
があります。ここが評価されればブラウザは再読込します。
さらにsocket
の定義を探しました。
const socketProtocol = null || (location.protocol === 'https:' ? 'wss' : 'ws');
const socketHost = `${null || location.hostname}:${"24678/_nuxt/"}`;
const socket = new WebSocket(`${socketProtocol}://${socketHost}`, 'vite-hmr');
先にログで確認したFirefox can’t establish a connection to the server at ws://{LXCのIPアドレス}:24678/_nuxt/.
を補強する内容です。24678番ポートを使ってWebSocketを使おうとしています。このWebSocketが閉じられると、ブラウザが再読込するよう書かれていますので、本症状に合致します。
Docker-ComposeにWebSocketのポートもマッピングさせて解決
docker-compose.yamlに24678番ポートについて追記し、Docker-Composeを再起動します。
version: "3.8"
services:
mycontainer:
ports:
- 3000:3000
- 24678:24678
これでViteでブラウザが再読込が繰り返す問題は解決しました。