| name | hot-reload-optimizer |
| description | Optimizes hot module replacement and fast refresh for development speed with Vite, Next.js, and webpack configurations. Use when users request "hot reload", "HMR optimization", "fast refresh", "dev server speed", or "development performance". |
Hot Reload Optimizer
Optimize development experience with fast hot module replacement.
Core Workflow
- Analyze bottlenecks: Identify slow rebuilds
- Configure HMR: Framework-specific setup
- Optimize bundler: Exclude heavy dependencies
- Setup caching: Persistent caching
- Monitor performance: Dev server metrics
- Fine-tune: Incremental improvements
Vite Configuration
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [
react({
jsxRuntime: 'automatic',
fastRefresh: true,
}),
tsconfigPaths(),
],
optimizeDeps: {
include: [
'react',
'react-dom',
'react-router-dom',
'@tanstack/react-query',
'zustand',
'lodash-es',
],
exclude: ['@vite/client'],
force: false,
entries: ['./src/**/*.{ts,tsx}'],
},
server: {
port: 3000,
hmr: {
overlay: true,
protocol: 'ws',
host: 'localhost',
},
watch: {
usePolling: false,
ignored: ['**/node_modules/**', '**/.git/**'],
},
warmup: {
clientFiles: ['./src/main.tsx', './src/App.tsx'],
},
},
build: {
sourcemap: true,
chunkSizeWarningLimit: 1000,
},
resolve: {
alias: {
'@': '/src',
},
},
css: {
devSourcemap: true,
modules: {
localsConvention: 'camelCase',
},
},
define: {
__DEV__: JSON.stringify(process.env.NODE_ENV !== 'production'),
},
});
Vite with SWC
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
export default defineConfig({
plugins: [
react({
jsxImportSource: '@emotion/react',
plugins: [
['@swc/plugin-emotion', {}],
],
}),
],
});
Next.js Configuration
const nextConfig = {
swcMinify: true,
experimental: {
optimizePackageImports: [
'@heroicons/react',
'lucide-react',
'date-fns',
'lodash',
],
},
webpack: (config, { dev, isServer }) => {
if (dev) {
config.devtool = 'eval-source-map';
config.watchOptions = {
ignored: ['**/node_modules/**', '**/.git/**'],
aggregateTimeout: 200,
poll: false,
};
config.cache = {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
};
}
return config;
},
typescript: {
ignoreBuildErrors: process.env.NODE_ENV === 'development',
},
eslint: {
ignoreDuringBuilds: process.env.NODE_ENV === 'development',
},
images: {
domains: ['example.com'],
deviceSizes: [640, 750, 828, 1080, 1200],
},
};
module.exports = nextConfig;
Next.js Turbopack
const nextConfig = {
experimental: {
turbo: {
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
resolveAlias: {
'@': './src',
},
},
},
};
module.exports = nextConfig;
next dev --turbo
Webpack Configuration
const path = require('path');
const webpack = require('webpack');
const ReactRefreshPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
module.exports = (env, argv) => {
const isDev = argv.mode === 'development';
return {
mode: isDev ? 'development' : 'production',
devtool: isDev ? 'eval-cheap-module-source-map' : 'source-map',
entry: './src/index.tsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: isDev ? '[name].js' : '[name].[contenthash].js',
clean: true,
},
cache: isDev ? {
type: 'filesystem',
cacheDirectory: path.resolve(__dirname, '.cache'),
buildDependencies: {
config: [__filename],
},
} : false,
resolve: {
extensions: ['.tsx', '.ts', '.js'],
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
module: {
rules: [
{
test: /\.[jt]sx?$/,
exclude: /node_modules/,
use: {
loader: 'swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
tsx: true,
},
transform: {
react: {
runtime: 'automatic',
development: isDev,
refresh: isDev,
},
},
},
},
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader'],
},
],
},
plugins: [
isDev && new ReactRefreshPlugin({
overlay: {
sockIntegration: 'whm',
},
}),
isDev && new ForkTsCheckerWebpackPlugin({
async: true,
typescript: {
configFile: path.resolve(__dirname, 'tsconfig.json'),
diagnosticOptions: {
semantic: true,
syntactic: true,
},
},
}),
isDev && new webpack.HotModuleReplacementPlugin(),
].filter(Boolean),
devServer: {
port: 3000,
hot: true,
liveReload: false,
client: {
overlay: {
errors: true,
warnings: false,
},
progress: true,
},
static: {
directory: path.join(__dirname, 'public'),
},
historyApiFallback: true,
compress: true,
watchFiles: ['src/**/*'],
},
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
performance: {
hints: isDev ? false : 'warning',
},
stats: isDev ? 'errors-warnings' : 'normal',
};
};
React Fast Refresh Boundaries
import { Component, type ErrorInfo, type ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
}
export class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(): State {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? <div>Something went wrong</div>;
}
return this.props.children;
}
}
import { useState } from 'react';
export function App() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
CSS HMR Optimization
export default defineConfig({
css: {
devSourcemap: true,
postcss: {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
],
},
modules: {
localsConvention: 'camelCase',
generateScopedName: '[name]__[local]___[hash:base64:5]',
},
},
});
module.exports = {
content: ['./src/**/*.{js,ts,jsx,tsx}'],
corePlugins: {
preflight: true,
},
};
Development Scripts
{
"scripts": {
"dev": "vite --host",
"dev:debug": "DEBUG=vite:* vite",
"dev:profile": "vite --profile",
"dev:force": "vite --force",
"analyze": "vite-bundle-analyzer",
"typecheck": "tsc --noEmit --watch",
"typecheck:fast": "tsc --noEmit --incremental"
}
}
Performance Monitoring
import { defineConfig, type Plugin } from 'vite';
function hmrTimingPlugin(): Plugin {
let startTime: number;
return {
name: 'hmr-timing',
handleHotUpdate({ file }) {
startTime = performance.now();
console.log(`[HMR] File changed: ${file}`);
return;
},
transform() {
if (startTime) {
const duration = performance.now() - startTime;
console.log(`[HMR] Transform took: ${duration.toFixed(2)}ms`);
}
return null;
},
};
}
export default defineConfig({
plugins: [hmrTimingPlugin()],
});
if (import.meta.hot) {
let lastUpdate = Date.now();
import.meta.hot.on('vite:beforeUpdate', () => {
lastUpdate = Date.now();
console.log('[HMR] Update starting...');
});
import.meta.hot.on('vite:afterUpdate', () => {
const duration = Date.now() - lastUpdate;
console.log(`[HMR] Update complete in ${duration}ms`);
});
import.meta.hot.on('vite:error', (error) => {
console.error('[HMR] Error:', error);
});
}
Docker Development
# Dockerfile.dev
FROM node:20-alpine
WORKDIR /app
# Install dependencies separately for caching
COPY package*.json ./
RUN npm ci
# Copy source
COPY . .
# Use polling for file watching in Docker
ENV CHOKIDAR_USEPOLLING=true
ENV WATCHPACK_POLLING=true
EXPOSE 3000
CMD ["npm", "run", "dev"]
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- ./src:/app/src
- ./public:/app/public
- /app/node_modules
ports:
- "3000:3000"
environment:
- CHOKIDAR_USEPOLLING=true
- WATCHPACK_POLLING=true
Best Practices
- Use SWC/esbuild: Faster than Babel
- Pre-bundle dependencies: Avoid re-bundling
- Filesystem caching: Persist build cache
- Separate type checking: Fork process
- Optimize imports: Tree-shake properly
- Minimize watchers: Exclude unnecessary files
- Use Turbopack: When stable in Next.js
- Profile regularly: Identify bottlenecks
Output Checklist
Every HMR optimization should include: