# How to Fix "Hot Module Reload Seems to Have Frozen" in the Chrome Extension React Vite Boilerplate

> Fix the 'Hot module reload seems to have frozen' error in your React Vite Chrome extension. Resolve WebSocket conflicts by killing port 8081 processes and restarting your dev server for seamless live reloading.

- Repository: [JongHak Seo/chrome-extension-boilerplate-react-vite](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite)
- Tags: troubleshooting
- Published: 2026-03-05

---

**Kill any process bound to port 8081 and restart `pnpm dev` to clear the WebSocket conflict and restore live reloading.**

The `jonghakseo/chrome-extension-boilerplate-react-vite` repository ships a custom Hot Module Replacement (HMR) system that coordinates across background service workers, UI pages, and content scripts. When this pipeline breaks, edits to your source files no longer trigger automatic browser refreshes, leaving the extension stuck with stale code until you manually intervene.

## Understanding the HMR Architecture

The boilerplate implements a three-part reload pipeline that bridges Vite’s build system with Chrome’s extension runtime. According to the source code in `packages/hmr/`, the flow works as follows:

### WebSocket Server ([`init-reload-server.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/init-reload-server.ts))

Located at [`packages/hmr/lib/initializers/init-reload-server.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/hmr/lib/initializers/init-reload-server.ts) (lines 15–42), this component creates a WebSocket listener on **port 8081**. When Vite finishes rebuilding the bundle, the server broadcasts a `do_update` message to all connected clients. If the port is already occupied, the code explicitly logs an `EADDRINUSE` warning and aborts startup (lines 45–49), preventing the server from ever sending reload signals.

### Client Initializer ([`init-client.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/init-client.ts))

The [`packages/hmr/lib/initializers/init-client.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/hmr/lib/initializers/init-client.ts) file (lines 1–18) runs inside the extension context. It opens a persistent WebSocket connection to `ws://localhost:8081`, listens for the `do_update` event, executes the provided update callback, and acknowledges completion by sending `done_update` back to the server.

### Reload Injection ([`reload.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/reload.ts))

The [`packages/hmr/lib/injections/reload.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/hmr/lib/injections/reload.ts) file (lines 1–13) contains the runtime code injected into the extension bundle. When the client initializer receives an update signal, this script calls `chrome.runtime.reload()` to refresh the entire extension or triggers module-specific updates for content scripts.

## Root Cause of the Freeze

The "frozen" state occurs when the **WebSocket server cannot bind to port 8081**, most commonly because a previous development instance is still running. Since the client-side code in [`init-client.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/init-client.ts) never receives the `do_update` message without an active server connection, the extension remains unaware of file changes.

This conflict typically surfaces in two scenarios:

- **Orphaned Dev Server**: A `pnpm dev` process was interrupted (Ctrl+C) but left child processes holding the socket.
- **Stray Turborepo Processes**: If you see `grpc` errors in the terminal, a previous Turborepo (`turbo`) instance is still managing the monorepo pipeline and occupying the HMR port.

## Step-by-Step Recovery

Use these terminal commands to clear the port conflict and restore HMR functionality.

### Method 1: Clean Server Restart

First, stop the current development session. If the terminal is unresponsive, force-kill the process:

```bash

# Stop the current dev server

pkill -f "pnpm dev"

# Alternatively, kill by port

kill $(lsof -t -i:8081)

# Restart the development environment

pnpm dev

```

### Method 2: Kill Stray Turborepo Processes

When the freeze coincides with gRPC errors, terminate the lingering `turbo` process before restarting:

```bash

# Remove the stray process holding the HMR socket

pkill -f "turbo"

# Launch a fresh instance

pnpm dev

```

### Method 3: One-Liner Port Reset

For a single command that clears port 8081 and immediately restarts development:

```bash
kill $(lsof -t -i:8081) && pnpm dev

```

## Key Configuration Files

These source files define the HMR behavior and are useful for debugging custom modifications:

| File | Purpose |
|------|---------|
| [`packages/hmr/lib/consts.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/hmr/lib/consts.ts) | Defines the WebSocket port (`8081`) and message type constants (`do_update`, `done_update`). |
| [`packages/hmr/lib/initializers/init-reload-server.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/hmr/lib/initializers/init-reload-server.ts) | Initializes the WebSocket server and handles connection cleanup. |
| [`packages/hmr/lib/initializers/init-client.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/hmr/lib/initializers/init-client.ts) | Manages the client-side WebSocket connection and update callbacks. |
| [`packages/hmr/lib/injections/reload.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/hmr/lib/injections/reload.ts) | Contains the `chrome.runtime.reload()` trigger injected into the extension bundle. |

## Summary

- The HMR freeze is a **port-conflict issue**, not a code error.
- The WebSocket server in [`init-reload-server.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/init-reload-server.ts) requires exclusive access to **port 8081** to broadcast `do_update` signals.
- **Restarting `pnpm dev`** forces the server to re-bind, provided no lingering process occupies the port.
- **Killing stray `turbo` processes** resolves gRPC-related conflicts that prevent the server from starting.
- The reload pipeline depends on three coordinated components: the server initializer, the client initializer, and the runtime injection script.

## Frequently Asked Questions

### What specific port does the HMR server use?

The server listens on **port 8081**, as defined in [`packages/hmr/lib/consts.ts`](https://github.com/jonghakseo/chrome-extension-boilerplate-react-vite/blob/main/packages/hmr/lib/consts.ts). If this port is unavailable, the server logs an `EADDRINUSE` error and the extension never receives reload notifications.

### Why does Turborepo cause the HMR to freeze?

Turborepo manages the monorepo pipeline across multiple packages. If a previous `turbo` process crashes or hangs, it continues holding the WebSocket port, blocking new instances from broadcasting `do_update` messages to the extension client.

### How can I verify what process is blocking port 8081?

Run `lsof -i:8081` on macOS/Linux or `netstat -ano \| findstr :8081` on Windows to identify the process ID. Once identified, terminate it with `kill <PID>` (Unix) or `taskkill /PID <PID> /F` (Windows) before restarting `pnpm dev`.

### Will killing the `turbo` process affect other projects?

Terminating a stray `turbo` process only affects the specific monorepo instance that was frozen. Other active Turborepo sessions on different ports or directories will continue running normally. Always verify the process ID with `lsof` to ensure you are targeting the correct instance.