# How to Set Up Hot Module Replacement (HMR) in Bun for Automatic Development Reloads

> Learn how to set up Hot Module Replacement HMR in Bun for automatic development reloads. Utilize import.meta.hot and Bun.serve for seamless module updates without page refreshes.

- Repository: [Bun/bun](https://github.com/oven-sh/bun)
- Tags: how-to-guide
- Published: 2026-02-28

---

**Bun provides built-in Hot Module Replacement that activates when you pass `development: true` and `hmr: true` to `Bun.serve`, using a WebSocket at `/_bun/hmr` to push updates and the `import.meta.hot` API to handle module swaps without full page reloads.**

Setting up **Hot Module Replacement (HMR)** for development in the oven-sh/bun repository requires no external bundlers or plugins. Bun ships with a complete, integrated HMR system that coordinates between the server runtime and browser to deliver instant updates as you edit source files.

## How Bun's HMR Architecture Works

Bun's HMR implementation consists of three coordinated components located in `src/bake/`. The **server-side runtime** ([`src/bake/hmr-runtime-server.ts`](https://github.com/oven-sh/bun/blob/main/src/bake/hmr-runtime-server.ts)) initializes a WebSocket endpoint at `/_bun/hmr` and registers the `registerUpdate` callback to listen for file changes. When the bundler emits a hot update, the server invokes `replaceModules` from [`src/bake/hmr-module.ts`](https://github.com/oven-sh/bun/blob/main/src/bake/hmr-module.ts) to merge new module definitions into the registry. The **client-side runtime** ([`src/bake/hmr-runtime-client.ts`](https://github.com/oven-sh/bun/blob/main/src/bake/hmr-runtime-client.ts)) maintains a WebSocket connection, receives update payloads, and injects new script tags to execute the hot swap.

## Configuring the Development Server

To enable automatic reloads, configure `Bun.serve` with the `development` and `hmr` options set to `true`. According to the type definitions in [`src/bake/bake.d.ts`](https://github.com/oven-sh/bun/blob/main/src/bake/bake.d.ts), these flags instruct the runtime to enable dev-mode diagnostics and embed the HMR client runtime into the bundle.

```typescript
// server.ts
import { serve } from "bun";

serve({
  development: true, // Required for HMR diagnostics
  hmr: true,         // Enables Hot Module Replacement
  port: 3000,
  fetch(req) {
    return new Response("Hello World");
  },
});

```

Executing `bun run server.ts` starts the development environment. The runtime automatically bundles your code with HMR support and opens the WebSocket channel for pushing updates to connected browsers.

## Using the import.meta.hot API

The HMR runtime exposes a **hot context** via `import.meta.hot` that allows modules to participate in the update process. This API is implemented in [`src/bake/hmr-module.ts`](https://github.com/oven-sh/bun/blob/main/src/bake/hmr-module.ts) and provides methods for accepting updates, cleaning up resources, and persisting state across reloads.

### Self-Accepting Modules

The simplest pattern involves a module accepting its own replacement. When the file changes, Bun executes the `accept` callback, allowing you to re-render components or re-initialize logic without losing application state.

```typescript
// components/Counter.tsx
export function Counter() { /* ... */ }

if (import.meta.hot) {
  import.meta.hot.accept(() => {
    console.log("Counter updated - re-render if needed");
  });
}

```

If a module does not register an accept handler, Bun safely falls back to a full page reload by calling `fullReload()`, which emits the `bun:beforeFullReload` event before executing `location.reload()`.

### Accepting Dependency Updates

Modules can handle updates from specific imports by passing a module specifier to `accept`. The callback receives the updated module namespace, allowing you to replace local references.

```typescript
// pages/Home.tsx
import { Header } from "./Header";

export function Home() { /* ... */ }

if (import.meta.hot) {
  import.meta.hot.accept("./Header", (newHeader) => {
    // Replace the imported binding with the new module
    Header = newHeader.Header;
  });
}

```

### Cleanup with Dispose Handlers

Before a module is replaced, Bun invokes any registered **dispose handlers** to clean up timers, event listeners, or subscriptions. The `import.meta.hot.data` object persists between disposes, enabling you to maintain state across reloads.

```typescript
if (import.meta.hot) {
  import.meta.hot.dispose(() => {
    clearInterval(pollingTimer);
    subscription.unsubscribe();
  });
}

```

## Server-Side Update Flow

When a source file changes, the server-side logic in [`src/bake/hmr-runtime-server.ts`](https://github.com/oven-sh/bun/blob/main/src/bake/hmr-runtime-server.ts) triggers the bundler to generate a hot update chunk. The system calls `replaceModules(modules, sourceMapId)` from [`src/bake/hmr-module.ts`](https://github.com/oven-sh/bun/blob/main/src/bake/hmr-module.ts), which:

- Merges new module definitions into `unloadedModuleRegistry`
- Walks the module graph to locate HMR boundaries
- Executes dispose callbacks on modules being replaced
- Triggers `refreshRuntime.performReactRefresh()` if React Fast Refresh is loaded
- Signals the client to perform a `fullReload()` if the update cannot be safely applied

## Client-Side Update Delivery

Upon page load, the client runtime in [`src/bake/hmr-runtime-client.ts`](https://github.com/oven-sh/bun/blob/main/src/bake/hmr-runtime-client.ts) opens a WebSocket connection to `/_bun/hmr` and listens for `MessageId.hot_update` payloads. When an update arrives, the client:

1. Parses the payload using `DataViewReader`
2. Updates CSS via `editCssArray` or `editCssContent`
3. Creates a new `<script>` tag with a Blob URL containing the updated JavaScript
4. Invokes the global `bun:hmr` function once the script loads, which calls `replaceModules` on the client-side registry
5. Executes any `import.meta.hot.accept` callbacks to complete the hot swap

## Complete Working Example

The following setup demonstrates a React application with HMR enabled. The entry point accepts updates from the root component, while the development server handles bundling and static file serving.

```typescript
// main.tsx
import React from "React";
import { createRoot } from "react-dom/client";
import App from "./App";

const root = createRoot(document.getElementById("root")!);
root.render(<App />);

if (import.meta.hot) {
  import.meta.hot.accept("./App", (mod) => {
    root.render(<mod.App />);
  });
}

```

```typescript
// dev-server.ts
import { serve } from "bun";

serve({
  development: true,
  hmr: true,
  port: 3000,
  async fetch(req) {
    return await Bun.serveStatic(req, {
      directory: "./dist",
    });
  },
});

```

Run `bun run dev-server.ts` to start the environment. Editing [`App.tsx`](https://github.com/oven-sh/bun/blob/main/App.tsx) will instantly update the browser without a full page reload, preserving application state through the `import.meta.hot` lifecycle.

## Summary

- Enable HMR by setting `development: true` and `hmr: true` in your `Bun.serve` configuration according to [`src/bake/bake.d.ts`](https://github.com/oven-sh/bun/blob/main/src/bake/bake.d.ts).
- The runtime coordinates updates through three core files: [`hmr-runtime-server.ts`](https://github.com/oven-sh/bun/blob/main/hmr-runtime-server.ts), [`hmr-runtime-client.ts`](https://github.com/oven-sh/bun/blob/main/hmr-runtime-client.ts), and [`hmr-module.ts`](https://github.com/oven-sh/bun/blob/main/hmr-module.ts).
- Use `import.meta.hot.accept()` to handle module replacements without reloading the page.
- Implement `import.meta.hot.dispose()` for cleanup and use `import.meta.hot.data` to persist state across reloads.
- Bun automatically falls back to `fullReload()` when a module cannot be safely hot-replaced.

## Frequently Asked Questions

### Does Bun require Webpack or Vite to use HMR?

No. Bun includes a built-in HMR system that activates when you enable the `hmr: true` flag in your server configuration. The runtime handles bundling, WebSocket communication via `/_bun/hmr`, and module replacement internally without requiring external build tools.

### Why does my browser perform a full page reload instead of a hot update?

Bun performs a full page reload when a changed module lacks an `import.meta.hot.accept` handler or when a runtime error occurs during the update process. This behavior is implemented in [`src/bake/hmr-runtime-client.ts`](https://github.com/oven-sh/bun/blob/main/src/bake/hmr-runtime-client.ts) via the `fullReload()` function and ensures application stability when hot replacement is not possible.

### How do I preserve state during hot reloads in Bun?

Store state in the `import.meta.hot.data` object. This object survives the dispose cycle and is passed to the new module instance, allowing you to restore previous values after `import.meta.hot.accept` triggers. This mechanism is managed by the module registry in [`src/bake/hmr-module.ts`](https://github.com/oven-sh/bun/blob/main/src/bake/hmr-module.ts).

### Can I use Bun HMR with frameworks other than React?

Yes. While Bun automatically detects React and enables Fast Refresh via `refreshRuntime.performReactRefresh()`, the `import.meta.hot` API is framework-agnostic. You can use `accept` and `dispose` callbacks to manually trigger re-renders or re-initialization in Vue, Svelte, or vanilla JavaScript applications.