remix-run/remix · error · Error

nodeHmrRuntimeUnavailableError

Error message

nodeHmrRuntimeUnavailableError

What it means

In the fallback runtime implementation (runtime.ts), throwNodeHmrRuntimeUnavailable throws Error('The node-hmr/runtime API is only available when running inside node-hmr') whenever createBrowserHmrChannel or emitServerReady is invoked outside a node-hmr-supervised process. Unlike the eager module-level throw, this runtime loads but every API call fails, telling you the host process lacks the node-hmr runtime global.

Source

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

  async function createBrowserHmrChannel() {
    throwNodeHmrRuntimeUnavailable()
  }

/**
 * Notifies the `node-hmr` parent that this child process is ready to serve requests.
 *
 * Call this after the app server starts listening. After a restart, `node-hmr` waits for this
 * signal before publishing the browser `server:update` event, preventing clients from refreshing
 * against a server that is not ready yet.
 */
export const emitServerReady: NodeHmrRuntimeApi['emitServerReady'] = function emitServerReady() {
  throwNodeHmrRuntimeUnavailable()
}

throwNodeHmrRuntimeUnavailable()

function throwNodeHmrRuntimeUnavailable(): never {
  throw new Error(nodeHmrRuntimeUnavailableError)
}

View on GitHub (pinned to 9696913134)

Solutions

  1. Run the dev server through the node-hmr CLI so the runtime global exists
  2. Wrap createBrowserHmrChannel/emitServerReady calls in feature checks against the runtime global and skip them when absent
  3. Catch the error and degrade gracefully (skip browser HMR wiring / server-ready signaling)

Example fix

// before
runtime.emitServerReady()

// after
try {
  runtime.emitServerReady()
} catch {
  // not running under node-hmr; nothing to signal
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (globalThis.__nodeHmrRuntime !== undefined) {
  runtime.createBrowserHmrChannel()
  runtime.emitServerReady()
}

Type guard

function hasNodeHmrRuntime(): boolean {
  return typeof globalThis === 'object' && (globalThis as any).__nodeHmrRuntime !== undefined
}

Try / catch

try {
  runtime.emitServerReady()
} catch (error) {
  if (error instanceof Error && error.message.includes('only available when running inside node-hmr')) {
    // not under node-hmr — skip
  } else throw error
}

Prevention

When it happens

Trigger: Calling remixNodeHmrRuntime.createBrowserHmrChannel() or .emitServerReady() when getNodeHmrRuntime() returned undefined — i.e. any process not spawned/registered by node-hmr.

Common situations: Dev scripts bypassing the node-hmr CLI; unit tests exercising code that calls emitServerReady(); environments (CI) where the node-hmr supervisor global was never installed.

Related errors


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