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
- Start the dev process through node-hmr so a browser HMR event stream exists
- Enable browser HMR in the parent node-hmr configuration
- 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
- Start dev servers via the node-hmr CLI
- Guard browser-HMR wiring behind a runtime availability check
- Enable browser HMR in the parent node-hmr config when needed
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
- nodeHmrRuntimeUnavailableError
- hmr must create a channel with a close function
- hmr must create a channel with an onFileEvents function
- hmr must create a channel with an updateWatchedFiles functio
- nodeHmrRuntimeUnavailableError
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/38a2587f78c653f6.
Report an issue: GitHub.