---
type: package
title: remix/ui-hmr
---

# remix/ui-hmr

Hot module replacement transforms for Remix UI components, with integrations for Node and `remix/assets`.

`ui-hmr` rewrites supported Remix UI component modules so they can use the standard `import.meta.hot` APIs provided by packages like [`assets`](https://github.com/remix-run/remix/tree/main/packages/assets) and [`node-hmr`](https://github.com/remix-run/remix/tree/main/packages/node-hmr).

## Features

- **Stable Components** - Keep component identity stable while swapping implementations
- **Remount Fallbacks** - Mark components stale when their setup scope changes
- **Node Module Hooks** - Transform server component modules through Node's module customization hooks API
- **Assets Loader** - Transform component modules for the browser through `remix/assets`

## Installation

```sh
npm i remix
```

## Usage

Use `remix/ui-hmr/node` as a Node import hook for server modules:

```sh
node --import remix/node-tsx --import remix/ui-hmr/node ./server.ts
```

Use `uiHmr()` from `remix/ui-hmr/assets` with `remix/assets` for browser modules:

```ts
import { createAssetServer } from 'remix/assets'
import { uiHmr } from 'remix/ui-hmr/assets'

let isDevelopment = process.env.NODE_ENV === 'development'

let assetServer = createAssetServer({
  basePath: '/assets',
  fileMap: { '/app/*path': 'app/*path' },
  allowFiles: ['app/routes.ts', 'app/**/public/**'],
  allowPackages: ['remix'],
  denyFiles: ['app/**/*.test.*'],
  hmr: isDevelopment
    ? async () => (await import('remix/node-hmr/runtime')).createBrowserHmrChannel()
    : undefined,
  scripts: {
    loaders: isDevelopment ? [uiHmr()] : undefined,
  },
  watch: isDevelopment,
})
```

## Direct Transforms

Use the direct transform APIs when you are writing your own loader, Node module hooks, or build integration.

```ts
import { transformComponentsForBrowser } from 'remix/ui-hmr'

let result = transformComponentsForBrowser(source, {
  importSource: 'remix',
  moduleUrl: '/assets/app/routes.tsx',
})

if (result.transformed) {
  console.log(result.componentNames)
}
```

`transformComponentsForBrowser(source, options)` rewrites browser component modules and emits `import.meta.hot.accept()` code for browser updates. The direct transform uses `importSource` to derive imports for the UI refresh runtime and browser HMR runtime.

```ts
import { transformComponentsForServer } from 'remix/ui-hmr'

let result = transformComponentsForServer(source, {
  importSource: 'remix',
  moduleUrl: 'file:///app/routes.tsx',
})
```

`transformComponentsForServer(source, options)` rewrites server component modules and registers updated component implementations for the current module URL. The direct transform uses `importSource` to derive imports for the server HMR runtime.

Both transforms return:

```ts
type ComponentsHmrTransformResult = {
  code: string
  componentNames: string[]
  map: string | null
  transformed: boolean
}
```

Pass `sourceMap: true` to generate a source map.

Use `importSource` to define where injected imports come from, typically either `remix` or `@remix-run`:

```ts
transformComponentsForBrowser(source, {
  importSource: 'remix',
  moduleUrl: '/assets/app/routes.tsx',
})

transformComponentsForServer(source, {
  importSource: 'remix',
  moduleUrl: 'file:///app/routes.tsx',
})
```

`importSource: 'remix'` generates imports from `remix/ui` and `remix/ui-hmr`. `importSource: '@remix-run'` generates imports from `@remix-run/ui` and `@remix-run/ui-hmr`. Custom import sources follow the same nested import layout.

## Related Packages

- [`assets`](https://github.com/remix-run/remix/tree/main/packages/assets) - Runs loaders while compiling assets
- [`node-hmr`](https://github.com/remix-run/remix/tree/main/packages/node-hmr) - Provides the server-side `import.meta.hot` runtime
- [`ui`](https://github.com/remix-run/remix/tree/main/packages/ui) - Component APIs transformed by `ui-hmr`

## License

See [LICENSE](https://github.com/remix-run/remix/blob/main/LICENSE)

