remix-run/remix · error · TypeError
hmr must create a channel with an onFileEvents function
Error message
hmr must create a channel with an onFileEvents function
What it means
The HMR channel returned by `hmr.createChannel` must expose an `onFileEvents` function so the asset server can subscribe to file change events for hot updates. This error fires when the channel object is missing that method or it is not callable.
Source
Thrown at packages/assets/src/lib/asset-server.ts:1130
}
setUnsubscribe(channel.onFileEvents(handleFileEvents))
return channel
})
}
function validateBrowserHmrChannel(channel: unknown): asserts channel is BrowserHmrChannel {
if (channel === null || typeof channel !== 'object') {
throw new TypeError('hmr must create an object')
}
if (!('url' in channel) || typeof channel.url !== 'string') {
throw new TypeError('hmr must create a channel with a string url')
}
if (!('close' in channel) || typeof channel.close !== 'function') {
throw new TypeError('hmr must create a channel with a close function')
}
if (!('onFileEvents' in channel) || typeof channel.onFileEvents !== 'function') {
throw new TypeError('hmr must create a channel with an onFileEvents function')
}
if (!('updateWatchedFiles' in channel) || typeof channel.updateWatchedFiles !== 'function') {
throw new TypeError('hmr must create a channel with an updateWatchedFiles function')
}
}
function normalizeBasePath(basePath: string): string {
if (typeof basePath !== 'string') {
throw new TypeError('basePath must be a string')
}
return normalizePathname(basePath || '/').replace(/\/+$/, '') || '/'
}
function normalizeFingerprintOptions(options: {
fingerprint: AssetServerOptions['fingerprint']
watch: AssetServerOptions['watch']
}):View on GitHub (pinned to 9696913134)
Solutions
- Implement `onFileEvents(callback: (events: FileEvent[]) => void)` on the returned channel, storing the callback and invoking it when files change
- Confirm the other required members (`url`, `close`, `updateWatchedFiles`) exist too
- Drop the custom `createChannel` if the default HMR channel suffices
Example fix
// before
createChannel: () => ({ url, close, updateWatchedFiles })
// after
createChannel: () => ({
url,
close,
updateWatchedFiles,
onFileEvents(callback) {
watcher.on('change', (file) => callback([{ type: 'update', file }]))
},
}) Defensive patterns
Strategy: validation
Validate before calling
let channel = createChannel()
if (typeof channel.onFileEvents !== 'function') throw new Error('channel must implement onFileEvents') Type guard
function hasOnFileEvents(c: unknown): c is { onFileEvents: (cb: (events: unknown[]) => void) => void } {
return !!c && typeof (c as any).onFileEvents === 'function'
} Prevention
- Implement the full channel interface in one place (a base class or factory) so all members stay in sync
- Run the dev server once with the custom channel in CI to catch contract violations early
When it happens
Trigger: `createChannel` returns an object without an `onFileEvents(callback)` method, so the asset server cannot register its file-event listener.
Common situations: Custom channel wrappers (e.g. around a bundler watcher or SSE stream) that only implement `url`/`close`; channels ported from older Remix versions; incomplete test doubles.
Related errors
- hmr must create a channel with an updateWatchedFiles functio
- hmr must create a channel with a close function
- Browser HMR is disabled for this node-hmr runtime
- basePath must be a string
- fingerprint.buildId must be a string
AI-assisted analysis of remix-run/remix@9696913134 (2026-08-27).
Data as JSON: /api/errors/35fb0ec4e0430a32.
Report an issue: GitHub.