---
title: "React Refresh Support"
description: "React Refresh Support (commonly known as Fast Refresh) in Next.js provides instant feedback during local development by updating React components in the browser without losing component state. The ..."
last_updated: "2026-09-23T10:52:03.186406+00:00"
canonical_url: "https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/development-and-diagnostics/react-refresh-support"
---

<details>
<summary>Relevant source files</summary>

The following files were used as context for generating this wiki page:

- [packages/next/src/server/dev/hot-reloader-webpack.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-webpack.ts)
- [packages/react-refresh-utils/internal/RspackReactRefresh.ts](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/internal/RspackReactRefresh.ts)
- [packages/react-refresh-utils/ReactRefreshWebpackPlugin.ts](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/ReactRefreshWebpackPlugin.ts)
- [packages/next/src/server/dev/hot-reloader-turbopack.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-turbopack.ts)
- [packages/react-refresh-utils/ReactRefreshRspackPlugin.ts](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/ReactRefreshRspackPlugin.ts)
- [packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx)
- [packages/react-refresh-utils/internal/ReactRefreshModule.runtime.ts](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/internal/ReactRefreshModule.runtime.ts)
- [packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/pages/hot-reloader-pages.ts)
- [packages/react-refresh-utils/internal/helpers.ts](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/internal/helpers.ts)
- [packages/next/src/server/dev/on-demand-entry-handler.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/on-demand-entry-handler.ts)
- [packages/next/src/bundles/webpack/packages/HotModuleReplacement.runtime.js](https://github.com/blade47/next.js/blob/main/packages/next/src/bundles/webpack/packages/HotModuleReplacement.runtime.js)
- [packages/react-refresh-utils/rspack-runtime.ts](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/rspack-runtime.ts)
- [packages/react-refresh-utils/loader.ts](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/loader.ts)
- [packages/next/src/bundles/webpack/packages/JavascriptHotModuleReplacement.runtime.js](https://github.com/blade47/next.js/blob/main/packages/next/src/bundles/webpack/packages/JavascriptHotModuleReplacement.runtime.js)
- [packages/react-refresh-utils/runtime.ts](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/runtime.ts)
- [packages/react-refresh-utils/react-refresh-runtime.d.ts](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/react-refresh-runtime.d.ts)
- [packages/next/src/server/dev/hot-reloader-rspack.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-rspack.ts)
- [packages/next/src/bundles/webpack/packages/lazy-compilation-web.js](https://github.com/blade47/next.js/blob/main/packages/next/src/bundles/webpack/packages/lazy-compilation-web.js)
- [packages/next/src/server/lib/router-utils/setup-dev-bundler.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts)
- [packages/next/next-devtools.webpack-config.js](https://github.com/blade47/next.js/blob/main/packages/next/next-devtools.webpack-config.js)
- [packages/next/src/client/dev/hot-reloader/turbopack-hot-reloader-common.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/turbopack-hot-reloader-common.ts)
- [packages/react-refresh-utils/tsconfig.json](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/tsconfig.json)
- [packages/next/src/client/dev/hot-reloader/shared.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/shared.ts)
- [packages/next/src/client/dev/noop-turbopack-hmr.ts](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/noop-turbopack-hmr.ts)
- [packages/react-refresh-utils/package.json](https://github.com/blade47/next.js/package.json)
</details>

## Overview

React Refresh Support (commonly known as Fast Refresh) in Next.js provides instant feedback during local development by updating React components in the browser without losing component state. The system bridges lower-level Hot Module Replacement (HMR) mechanisms provided by Webpack and Rspack with the official `react-refresh` runtime library. By instrumenting module execution, registering React component families, and tracking exports signatures, the framework can surgically refresh updated components while falling back to full reloads when signature changes or side effects violate safety invariants.

Sources: [packages/react-refresh-utils/package.json:1-32](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/package.json#L1-L32)

The subsystem consists of three major structural pillars: runtime integration utilities (`@next/react-refresh-utils`), bundler-specific plugins (`ReactFreshWebpackPlugin` for Webpack and `ReactRefreshRspackPlugin` for Rspack), and client-side HMR reconciliation handlers (`hot-reloader-app.tsx`, `hot-reloader-pages.ts`). These layers cooperate to intercept module execution, evaluate whether exported entities qualify as React refresh boundaries, schedule updates via `module.hot`, and communicate status changes to the developer overlay.

Sources: [packages/react-refresh-utils/runtime.ts:1-35](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/runtime.ts#L1-L35)

---

## Bundler Integration and Webpack Plugins

The `ReactFreshWebpackPlugin` handles Webpack 4 and Webpack 5 integrations by hooking into compilation lifecycles to inject necessary runtime helpers and intercept module execution. For Webpack 5, the plugin appends a `ReactRefreshRuntimeModule` to runtime requirements via `compilation.hooks.additionalTreeRuntimeRequirements`.

Sources: [packages/react-refresh-utils/ReactRefreshWebpackPlugin.ts:88-102](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/ReactRefreshWebpackPlugin.ts#L88-L102)

This runtime module hooks into `RuntimeGlobals.interceptModuleExecution` to wrap original module factories.

Sources: [packages/react-refresh-utils/ReactRefreshWebpackPlugin.ts:98-107](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/ReactRefreshWebpackPlugin.ts#L98-L107)

When a module executes, the wrapper establishes execution interception via `self.$RefreshInterceptModuleExecution$(moduleId)`. This temporary hook re-binds `self.$RefreshReg$` and `self.$RefreshSig$` to register component types and transform signature functions against the specific `webpackModuleId`. A `try...finally` block guarantees cleanup even if module execution throws an error.

Sources: [packages/react-refresh-utils/ReactRefreshWebpackPlugin.ts:118-135](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/ReactRefreshWebpackPlugin.ts#L118-L135)

```typescript
// Example wiring in Webpack 5 compilation hook
compiler.hooks.compilation.tap('ReactFreshWebpackPlugin', (compilation) => {
  injectRefreshFunctions(compilation, Template)
  compilation.hooks.additionalTreeRuntimeRequirements.tap(
    'ReactFreshWebpackPlugin',
    (chunk: any) => {
      compilation.addRuntimeModule(chunk, new ReactRefreshRuntimeModule())
    }
  )
})
```

Sources: [packages/react-refresh-utils/ReactRefreshWebpackPlugin.ts:144-153](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/ReactRefreshWebpackPlugin.ts#L144-L153)

> [!NOTE]
> Webpack 4 lacks a native module execution interception API, so `ReactFreshWebpackPlugin` inspects and rewrites the source code of the template's require/evaluation block using string matching on `modules[moduleId].call(`.

Sources: [packages/react-refresh-utils/ReactRefreshWebpackPlugin.ts:33-85](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/ReactRefreshWebpackPlugin.ts#L33-L85)

---

## Rspack Runtime and Plugin Architecture

For Rspack environments, `ReactRefreshRspackPlugin` injects `$ReactRefreshRuntime$` globally via Rspack's `ProvidePlugin` and ensures `RuntimeGlobals.moduleCache` is present in the compilation's runtime requirements.

Sources: [packages/react-refresh-utils/ReactRefreshRspackPlugin.ts:5-20](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/ReactRefreshRspackPlugin.ts#L5-L20)

The companion runtime module (`rspack-runtime.ts`) initializes `react-refresh/runtime` against `self` using `RefreshRuntime.injectIntoGlobalHook(self)`, while assigning stub functions for `$RefreshSig$` and `$RefreshReg$` to prevent reference errors in uninstrumented modules.

Sources: [packages/react-refresh-utils/rspack-runtime.ts:6-19](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/rspack-runtime.ts#L6-L19)

```mermaid
flowchart TD
  A["Rspack Compilation Start"] --> B["ProvidePlugin: inject $ReactRefreshRuntime$"]
  B --> C["Tap additionalTreeRuntimeRequirements"]
  C --> D["Add moduleCache requirement"]
  D --> E["Initialize RefreshRuntime.injectIntoGlobalHook(self)"]
```

Sources: [packages/react-refresh-utils/ReactRefreshRspackPlugin.ts:8-21](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/ReactRefreshRspackPlugin.ts#L8-L21)

---

## Module Loader and Runtime Transformation

The `@next/react-refresh-utils` loader (`loader.ts`) appends module-level refresh execution logic to compiled `.ts`, `.tsx`, and `.js` files. It embeds `ReactRefreshModule.runtime.ts`, adapting `global.importMeta` to `import.meta` or `module.hot` depending on whether the file is CommonJS or ES module syntax.

Sources: [packages/react-refresh-utils/loader.ts:1-16](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/loader.ts#L1-L16)

The loader processes source files by calling `this.callback` with the original source combined with the un-wrapped runtime code.

Sources: [packages/react-refresh-utils/loader.ts:18-32](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/loader.ts#L18-L32)

The injected module runtime checks whether `self.$RefreshHelpers$` is available. If present, it retrieves `__webpack_module__.exports` and compares the previous module signature (`__webpack_module__.hot.data?.prevSignature`) against the current signature.

Sources: [packages/react-refresh-utils/internal/ReactRefreshModule.runtime.ts:25-35](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/internal/ReactRefreshModule.runtime.ts#L25-L35)

```typescript
// Runtime module injection sequence
var currentExports = __webpack_module__.exports
var prevSignature = __webpack_module__.hot.data?.prevSignature ?? null

self.$RefreshHelpers$.registerExportsForReactRefresh(currentExports, __webpack_module__.id)

if (self.$RefreshHelpers$.isReactRefreshBoundary(currentExports)) {
  __webpack_module__.hot.dispose(function (data) {
    data.prevSignature = self.$RefreshHelpers$.getRefreshBoundarySignature(currentExports)
  })
  global.importMeta.webpackHot.accept()
}
```

Sources: [packages/react-refresh-utils/internal/ReactRefreshModule.runtime.ts:31-56](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/internal/ReactRefreshModule.runtime.ts#L31-L56)

---

## Refresh Boundaries and Invalidation Logic

Helpers in `packages/react-refresh-utils/internal/helpers.ts` determine whether a module constitutes a valid React Refresh boundary via `isReactRefreshBoundary()`. A module is a boundary if it exports components likely to be React components or if all its non-safe exports qualify as component types.

Sources: [packages/react-refresh-utils/internal/helpers.ts:111-137](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/internal/helpers.ts#L111-L137)

Safe exports (`__esModule`, `__N_SSG`, `__N_SSP`, and `config`) are ignored during inspection.

Sources: [packages/react-refresh-utils/internal/helpers.ts:52-60](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/internal/helpers.ts#L52-L60)

When an update occurs, `shouldInvalidateReactRefreshBoundary()` compares signature arrays. If the length or any element differs between `prevSignature` and `nextSignature`, the boundary is invalidated via `webpackHot.invalidate()`, triggering a wider reload cascade.

Sources: [packages/react-refresh-utils/internal/helpers.ts:139-152](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/internal/helpers.ts#L139-L152)

Otherwise, `scheduleUpdate()` batches updates for execution via `RefreshRuntime.performReactRefresh()`.

Sources: [packages/react-refresh-utils/internal/helpers.ts:154-176](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/internal/helpers.ts#L154-L176)

| Helper Function | Purpose | Condition / Return Value |
| :--- | :--- | :--- |
| `isSafeExport` | Filters framework metadata exports | Returns `true` for `__esModule`, `config`, etc. |
| `isReactRefreshBoundary` | Evaluates module export eligibility | Returns `true` if all exports are components |
| `shouldInvalidateReactRefreshBoundary` | Compares pre- and post-update signatures | Returns `true` if signature length or items diverge |
| `scheduleUpdate` | Batches and coordinates update triggers | Invokes `RefreshRuntime.performReactRefresh()` when idle |

Sources: [packages/react-refresh-utils/internal/helpers.ts:52-176](https://github.com/blade47/next.js/blob/main/packages/react-refresh-utils/internal/helpers.ts#L52-L176)

> [!CAUTION]
> If a file exports both React components and non-component utilities consumed outside the React tree, editing it will cause Fast Refresh to trigger a full page reload (`REACT_REFRESH_FULL_RELOAD`).

Sources: [packages/next/src/client/dev/hot-reloader/shared.ts:3-10](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/shared.ts#L3-L10)

---

## Client-Side HMR Lifecycle and Error Handling

Client entrypoints (`hot-reloader-app.tsx` and `hot-reloader-pages.ts`) manage communication with the HMR server, monitor compilation hashes against `__webpack_hash__`, and apply updates.

Sources: [packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx:84-103](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx#L84-L103)

When updates arrive, `tryApplyUpdatesWebpack()` verifies that `module.hot.status() === 'idle'`. If updates cannot be applied immediately, `afterApplyUpdates()` registers a status handler to defer execution until the HMR status returns to `'idle'`.

Sources: [packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx:106-121](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx#L106-L121)

If runtime errors occur (`RuntimeErrorHandler.hadRuntimeError`), or if update application fails, `performFullReload()` logs stack traces and calls `window.location.reload()`.

Sources: [packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx:123-145](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx#L123-L145)

```typescript
// Webpack hot update application flow
function tryApplyUpdatesWebpack(sendMessage: (message: string) => void) {
  if (!isUpdateAvailable() || !canApplyUpdates()) {
    resolvePendingHotUpdateWebpack()
    dispatcher.onBuildOk()
    return
  }
  module.hot.check(true, function(err, updatedModules) {
    if (err || RuntimeErrorHandler.hadRuntimeError || updatedModules == null) {
      performFullReload(err, sendMessage)
      return
    }
    dispatcher.onRefresh()
  })
}
```

Sources: [packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx:148-179](https://github.com/blade47/next.js/blob/main/packages/next/src/client/dev/hot-reloader/app/hot-reloader-app.tsx#L148-L179)

---

## Rspack Persistent Cache and Built Entries Preservation

`HotReloaderRspack` extends `HotReloaderWebpack` to solve issues with Rspack's persistent caching model. While Webpack updates modules incrementally, Rspack operates on complete module graph snapshots.

Sources: [packages/next/src/server/dev/hot-reloader-rspack.ts:11-28](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-rspack.ts#L11-L28)

To prevent Rspack from purging the module graph on server restart, `HotReloaderRspack` maintains a `built-entries.json` cache file under the `dist/cache/rspack/` directory.

Sources: [packages/next/src/server/dev/hot-reloader-rspack.ts:29-69](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-rspack.ts#L29-L69)

During `afterCompile`, built entry files are read from cache, verified for existence and content hash changes via `calculateFileHash()`, and restored into the active compiler's entry map.

Sources: [packages/next/src/server/dev/hot-reloader-rspack.ts:70-139](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-rspack.ts#L70-L139)

```typescript
// Calculating file hash for Rspack persistent entry cache validation
async function calculateFileHash(
  filePath: string,
  algorithm: string = 'sha256'
): Promise<string | undefined> {
  if (!(await fs.access(filePath).then(() => true, () => false))) {
    return
  }
  const fileBuffer = await fs.readFile(filePath)
  const hash = createHash(algorithm)
  hash.update(fileBuffer)
  return hash.digest('hex')
}
```

Sources: [packages/next/src/server/dev/hot-reloader-rspack.ts:227-243](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-rspack.ts#L227-L243)

> [!WARNING]
> If a file listed in `built-entries.json` has been modified or deleted on disk since the last server stop, its hash validation will fail, causing the entry to be dropped from the restored cache to maintain graph integrity.

Sources: [packages/next/src/server/dev/hot-reloader-rspack.ts:91-136](https://github.com/blade47/next.js/blob/main/packages/next/src/server/dev/hot-reloader-rspack.ts#L91-L136)

## Related

- [Bundler Integration](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/development-and-diagnostics/bundler-integration)
- [Dev Server and HMR](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/technical/development-and-diagnostics/dev-server-and-hmr)


## Sitemap

See the full [sitemap](https://www.doc0.dev/docs/8f4009b0-65bd-4480-9b00-e201f0914bb3/llms.txt) for all pages in this wiki.
