remix-run/remix · error · Error
nodeHmrRuntimeUnavailableError
Error message
nodeHmrRuntimeUnavailableError
What it means
Importing node-hmr/runtime.node-hmr.ts immediately checks for the node-hmr runtime via globalThis; if absent it throws Error('The node-hmr/runtime API is only available when running inside node-hmr'). The module is designed to run only in child processes spawned by the node-hmr supervisor, so importing it elsewhere fails at import time.
Source
Thrown at packages/node-hmr/src/runtime.node-hmr.ts:9
import { emitServerReady, getNodeHmrRuntime } from './lib/runtime.ts'
import type { NodeHmrRuntimeApi } from './lib/runtime-api.ts'
import { nodeHmrRuntimeUnavailableError } from './lib/runtime-api.ts'
export type { BrowserHmrChannel } from './lib/browser-events.ts'
const maybeNodeHmrRuntime = getNodeHmrRuntime()
if (maybeNodeHmrRuntime === undefined) {
throw new Error(nodeHmrRuntimeUnavailableError)
}
const nodeHmrRuntime = maybeNodeHmrRuntime
/**
* Connects browser asset tooling in this child process to the browser HMR event stream and file
* watcher owned by its `node-hmr` parent process.
*
* Pass this function as the `hmr` factory for `createAssetServer()`. Each call creates an
* independent channel that must be closed when its owner shuts down. The returned promise rejects
* when browser HMR is disabled for the runner.
*
* The `remix/node-hmr/runtime` module itself can only be imported by a process supervised by
* `node-hmr`. Use a dynamic import guarded by `process.env.REMIX_NODE_HMR` when the same entry module
* also runs without HMR supervision.
*
* @returns A child-scoped channel for watching browser source files and publishing HMR events.
*/
export const createBrowserHmrChannel: NodeHmrRuntimeApi['createBrowserHmrChannel'] =View on GitHub (pinned to 9696913134)
Solutions
- Only import node-hmr/runtime from code that runs under the node-hmr supervisor (e.g. via register hooks it installs)
- Guard the import with await import() inside a check for the runtime global, so standalone execution can skip it
- Start your dev process with the node-hmr CLI entrypoint
Example fix
// before
import { createBrowserHmrChannel } from 'node-hmr/runtime' // throws at import outside node-hmr
// after
let nodeHmr = globalThis.__nodeHmrRuntime__
if (nodeHmr) {
let { createBrowserHmrChannel } = await import('node-hmr/runtime')
await createBrowserHmrChannel()
} Defensive patterns
Strategy: fallback
Validate before calling
if (typeof globalThis !== 'undefined' && '__nodeHmrRuntime' in globalThis) {
const runtime = await import('node-hmr/runtime')
await runtime.createBrowserHmrChannel()
} Type guard
const isNodeHmrChild = (): boolean => typeof globalThis === 'object' && globalThis !== null && '__nodeHmrRuntime' in globalThis
Try / catch
try {
await import('node-hmr/runtime')
} catch (error) {
if (error instanceof Error && error.message.includes('only available when running inside node-hmr')) {
// standalone execution — skip node-hmr features
} else throw error
} Prevention
- Import node-hmr/runtime dynamically, only after confirming the runtime global exists
- Run dev servers through the node-hmr CLI entrypoint
- Keep node-hmr imports out of test/build bundles
When it happens
Trigger: import-ing 'node-hmr/runtime' (runtime.node-hmr.ts) in a process not launched under node-hmr — plain node scripts, tests, or servers started without the node-hmr CLI — so getNodeHmrRuntime() returns undefined.
Common situations: Build/test tooling that transitively bundles or imports the runtime module outside dev; running the dev server with node instead of node-hmr; version changes making the module eager where it was lazy before.
Related errors
- Browser HMR is disabled for this node-hmr runtime
- nodeHmrRuntimeUnavailableError
- Invalid browser HMR channel port: ${port}
- import.meta.hot.accept() can only accept a callback, a strin
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/3846b1354f7c286d.
Report an issue: GitHub.