How to Configure Hot Module Replacement (HMR) in Storybook: Complete Vite Builder Guide
Storybook enables Hot Module Replacement (HMR) automatically when using the Vite builder, allowing you to configure the HMR port, protocol, or disable it entirely through the viteFinal function in your .storybook/main.ts configuration file.
Storybook's development server provides first-class support for Hot Module Replacement (HMR) to instantly reflect changes to components, stories, and addons without full page reloads. When you configure Hot Module Replacement (HMR) in Storybook using the default Vite builder—as implemented in the storybookjs/storybook repository—the system wires the HMR socket directly into the existing dev server. This guide explains how to customize HMR behavior, from basic port configuration to complete disablement for CI environments.
Understanding HMR in Storybook's Vite Builder
The Vite builder creates a dev server using createViteServer in code/builders/builder-vite/src/vite-server.ts. This file injects the HMR configuration directly into Vite's server options, binding the HMR socket to the same HTTP server that serves the Storybook UI.
Core HMR Wiring
In code/builders/builder-vite/src/vite-server.ts, the hmr object is configured to use the existing dev server instance and a specific port. This ensures that HMR updates flow through the same connection as your Storybook preview, avoiding cross-origin issues and port conflicts.
// code/builders/builder-vite/src/vite-server.ts
server: {
middlewareMode: true,
hmr: {
port,
server: devServer,
},
fs: {
strict: true,
},
},
How to Configure HMR in Your Storybook Project
You can customize HMR behavior through the viteFinal configuration hook in .storybook/main.ts. This function receives the final Vite configuration object and allows you to modify the server.hmr options before the dev server starts.
Basic Configuration (Default)
By default, Storybook automatically enables HMR when using the Vite builder. No additional configuration is required in your main.ts file. The builder automatically assigns an available port and wires it to the dev server according to the documentation in docs/_snippets/storybook-builder-api-configuration-options.md.
// .storybook/main.ts – minimal config, HMR works automatically
import type { StorybookConfig } from '@storybook/react-vite';
export default {
framework: '@storybook/react-vite',
core: { builder: '@storybook/builder-vite' },
} satisfies StorybookConfig;
Custom HMR Port and Protocol
To configure a specific HMR port or change the WebSocket protocol, modify the server.hmr object in viteFinal. This is useful when running Storybook behind a proxy or when the default port is already occupied.
// .storybook/main.ts – custom HMR options
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
framework: '@storybook/react-vite',
core: { builder: '@storybook/builder-vite' },
viteFinal: async (config) => {
config.server ??= {};
config.server.hmr = {
port: 3001, // custom port
protocol: 'ws', // force WebSocket protocol
};
return config;
},
};
export default config;
Disabling HMR for CI or Testing
For continuous integration environments or performance testing where instant updates are unnecessary, you can completely disable HMR by setting server.hmr to false. This prevents the WebSocket connection from being established and reduces resource consumption.
// .storybook/main.ts – turn off HMR
import type { StorybookConfig } from '@storybook/react-vite';
export default {
framework: '@storybook/react-vite',
core: { builder: '@storybook/builder-vite' },
viteFinal: async (config) => {
config.server ??= {};
config.server.hmr = false; // disables HMR
return config;
},
} satisfies StorybookConfig;
HMR for Storybook Addons
Addons running inside Storybook inherit the same HMR capabilities as your stories. The behavior differs slightly depending on whether the addon is developed locally alongside your project or distributed as a standalone package, as documented in docs/addons/addon-knowledge-base.mdx.
Local Addons
Local addons—those installed via file references or workspaces—automatically receive HMR updates without additional configuration. According to the addon knowledge base, changes to local addon code trigger instant updates in the Storybook UI because they share the same Vite dev server instance.
Standalone Addons
For standalone addons developed in separate repositories, enable HMR by running a watch script that rebuilds the addon while Storybook stays alive. While the addon rebuilds, Storybook picks up the new code through the file system. Configure this in your addon's package.json:
// addon/package.json
{
"name": "my-addon",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"start": "npm run build -- --watch"
},
"peerDependencies": {
"@storybook/addons": "^7.0.0"
}
}
Run npm start in the addon's folder, then launch Storybook in the parent repository. The addon will hot-reload as you edit its source code.
How Storybook Handles HMR in Story Files
Story files themselves should not be treated as HMR modules because Storybook already handles reloading stories at a higher level. To prevent duplicate reloads or conflicting HMR boundaries, the Vite builder includes a plugin that removes import.meta.hot.accept calls that may be emitted by bundlers.
In code/builders/builder-vite/src/plugins/strip-story-hmr-boundaries.ts, the plugin uses a regular expression to replace HMR acceptance calls with no-op functions:
// code/builders/builder-vite/src/plugins/strip-story-hmr-boundaries.ts
s.replace(/import\.meta\.hot\.accept\w*/, '(function hmrBoundaryNoop(){})');
This ensures that changes to .stories.js|ts|jsx|tsx files trigger a story reload through Storybook's internal mechanism rather than Vite's module replacement, maintaining consistent state and avoiding hydration mismatches.
Summary
- Automatic HMR: Storybook enables Hot Module Replacement automatically when using the Vite builder via
createViteServerincode/builders/builder-vite/src/vite-server.ts. - Configuration: Customize HMR port, protocol, or disable it entirely through the
viteFinalfunction in.storybook/main.ts. - Addon Support: Local addons inherit HMR automatically; standalone addons require a watch script for development.
- Story Handling: The Vite builder strips HMR boundaries from story files via
strip-story-hmr-boundaries.tsto prevent duplicate reloads.
Frequently Asked Questions
Is HMR enabled by default in Storybook?
Yes, when using the Vite builder (the default in recent Storybook versions), HMR is enabled automatically. The builder configures the HMR socket to share the same port as the Storybook dev server in code/builders/builder-vite/src/vite-server.ts, requiring no manual setup for standard development workflows.
How do I change the HMR port in Storybook?
Modify the server.hmr option in the viteFinal function within your .storybook/main.ts file. Set the port property to your desired port number to avoid conflicts with other services or to comply with corporate firewall rules.
Can I disable HMR in Storybook?
Yes, set server.hmr to false in your viteFinal configuration. This prevents the WebSocket connection from being established and is particularly useful for CI environments, static builds, or when debugging issues that might be caused by module replacement side effects.
Do Storybook addons support HMR?
Yes, local addons that are part of your workspace receive HMR automatically through the same Vite dev server instance. Standalone addons developed in separate repositories can support HMR by running a watch script (such as npm run build -- --watch) that rebuilds the addon while Storybook remains running, allowing the dev server to detect and serve the updated files.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →