remix-run/remix · error · Error

Browser HMR is disabled for this node-hmr runtime

Error message

Browser HMR is disabled for this node-hmr runtime

What it means

createBrowserHmrChannel() in the node-hmr runtime asks the parent node-hmr process for a browser event URL; if none is returned (the runtime is not running under node-hmr's supervision or browser HMR is disabled), the URL stays undefined and the method throws. It signals that browser asset tooling cannot connect because no browser HMR event stream exists for this child process.

Source

Thrown at packages/node-hmr/src/lib/runtime.ts:290

    }
  >()

  process.on('message', (message: unknown) => {
    if (!isBrowserHmrChannelMessage(message)) return

    let request = pendingBrowserHmrChannelRequests.get(message.requestId)
    if (request === undefined) return

    clearTimeout(request.timer)
    pendingBrowserHmrChannelRequests.delete(message.requestId)
    request.resolve(message.url)
  })

  let runtime: RemixNodeHmrRuntime = {
    async createBrowserHmrChannel() {
      browserEventUrl ??= await requestBrowserHmrChannelUrl()
      if (browserEventUrl === undefined) {
        throw new Error('Browser HMR is disabled for this node-hmr runtime')
      }

      let id = browserHmrChannelId++
      let watchedFiles = new Set<string>()
      let handlers = new Set<BrowserHmrFileEventHandler>()
      let closed = false
      browserHmrChannels.set(id, {
        async handleFileEvents(events) {
          let eventGroups = await Promise.all([...handlers].map((handler) => handler(events)))
          return eventGroups.flat()
        },
      })

      function updateWatchedFiles(delta: BrowserHmrWatchedFileDelta): void {
        if (closed) return

        for (let file of delta.add) {
          watchedFiles.add(file)

View on GitHub (pinned to 9696913134)

Solutions

  1. Start the dev process through node-hmr so a browser HMR event stream exists
  2. Enable browser HMR in the parent node-hmr configuration
  3. Feature-detect before use: only call createBrowserHmrChannel when the runtime advertises browser HMR (e.g. guard on runtime availability checks)

Example fix

// before
let channel = await runtime.createBrowserHmrChannel()

// after
let channel
try {
  channel = await runtime.createBrowserHmrChannel()
} catch {
  // running outside node-hmr; skip browser asset wiring
  channel = null
}
Defensive patterns

Strategy: try-catch

Validate before calling

// detect node-hmr supervision before wiring browser channel
let underNodeHmr = Boolean(globalThis.__nodeHmrRuntime) // runtime global installed by node-hmr
if (underNodeHmr) {
  let channel = await runtime.createBrowserHmrChannel()
}

Type guard

function isUnderNodeHmr(): boolean {
  return typeof globalThis !== 'undefined' && '__nodeHmrRuntime' in globalThis
}

Try / catch

try {
  channel = await runtime.createBrowserHmrChannel()
} catch (error) {
  if (error instanceof Error && error.message === 'Browser HMR is disabled for this node-hmr runtime') {
    channel = null // degrade gracefully
  } else throw error
}

Prevention

When it happens

Trigger: Calling remixNodeHmrRuntime.createBrowserHmrChannel() from a process started outside the node-hmr supervisor, or when the parent node-hmr run has browser HMR disabled, so requestBrowserHmrChannelUrl() resolves to undefined.

Common situations: Dev servers started with plain node/npm run instead of the node-hmr CLI; browser HMR disabled via config in the parent; plugins invoking browser channel APIs in standalone scripts or tests.

Related errors


AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27). Data as JSON: /api/errors/38a2587f78c653f6. Report an issue: GitHub.