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
// vite.config.ts import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({ plugins: [ react({ // Use SWC for faster transforms jsxRuntime: 'automatic', // Fast Refresh options fastRefresh: true, }), tsconfigPaths(), ],
// Optimize dependencies optimizeDeps: { // Pre-bundle these dependencies include: [ 'react', 'react-dom', 'react-router-dom', '@tanstack/react-query', 'zustand', 'lodash-es', ], // Exclude from pre-bundling exclude: ['@vite/client'], // Force re-optimization force: false, // Increase timeout for slow deps entries: ['./src/**/*.{ts,tsx}'], },
// Server configuration server: { port: 3000, // Enable HMR hmr: { overlay: true, protocol: 'ws', host: 'localhost', }, // Watch options watch: { // Use polling in containers/VMs usePolling: false, // Ignore patterns ignored: ['/node_modules/', '/.git/'], }, // Faster startup warmup: { clientFiles: ['./src/main.tsx', './src/App.tsx'], }, },
// Build optimizations for dev build: { // Source maps for development sourcemap: true, // Chunk size warnings chunkSizeWarningLimit: 1000, },
// Resolve configuration resolve: { alias: { '@': '/src', }, },
// CSS configuration css: { devSourcemap: true, modules: { localsConvention: 'camelCase', }, },
// Define environment variables define: { DEV: JSON.stringify(process.env.NODE_ENV !== 'production'), }, });
Vite with SWC
// vite.config.ts with SWC import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react-swc';
export default defineConfig({ plugins: [ react({ // SWC options jsxImportSource: '@emotion/react', plugins: [ // SWC plugins ['@swc/plugin-emotion', {}], ], }), ], });
Next.js Configuration
// next.config.js /** @type {import('next').NextConfig} */ const nextConfig = { // Enable SWC compiler swcMinify: true,
// Experimental features for faster dev experimental: { // Turbopack (when stable) // turbo: {},
// Optimize package imports
optimizePackageImports: [
'@heroicons/react',
'lucide-react',
'date-fns',
'lodash',
],
},
// Webpack customization webpack: (config, { dev, isServer }) => { if (dev) { // Faster source maps in development config.devtool = 'eval-source-map';
// Ignore large modules in watch
config.watchOptions = {
ignored: ['**/node_modules/**', '**/.git/**'],
aggregateTimeout: 200,
poll: false,
};
// Cache configuration
config.cache = {
type: 'filesystem',
buildDependencies: {
config: [__filename],
},
};
}
return config;
},
// Disable type checking during dev (use IDE) typescript: { ignoreBuildErrors: process.env.NODE_ENV === 'development', },
// Disable linting during dev (use IDE) eslint: { ignoreDuringBuilds: process.env.NODE_ENV === 'development', },
// Image optimization images: { domains: ['example.com'], deviceSizes: [640, 750, 828, 1080, 1200], }, };
module.exports = nextConfig;
Next.js Turbopack
// next.config.js with Turbopack /** @type {import('next').NextConfig} / const nextConfig = { experimental: { turbo: { rules: { '.svg': { loaders: ['@svgr/webpack'], as: '*.js', }, }, resolveAlias: { '@': './src', }, }, }, };
module.exports = nextConfig;
Run with Turbopack
next dev --turbo
Webpack Configuration
// webpack.config.js 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',
// Fast rebuild source maps
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,
},
// Caching for faster rebuilds
cache: isDev ? {
type: 'filesystem',
cacheDirectory: path.resolve(__dirname, '.cache'),
buildDependencies: {
config: [__filename],
},
} : false,
// Module resolution
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: [
// React Fast Refresh
isDev && new ReactRefreshPlugin({
overlay: {
sockIntegration: 'whm',
},
}),
// TypeScript type checking in separate process
isDev && new ForkTsCheckerWebpackPlugin({
async: true,
typescript: {
configFile: path.resolve(__dirname, 'tsconfig.json'),
diagnosticOptions: {
semantic: true,
syntactic: true,
},
},
}),
// HMR
isDev && new webpack.HotModuleReplacementPlugin(),
].filter(Boolean),
// Dev server configuration
devServer: {
port: 3000,
hot: true,
liveReload: false, // Use HMR instead
client: {
overlay: {
errors: true,
warnings: false,
},
progress: true,
},
static: {
directory: path.join(__dirname, 'public'),
},
historyApiFallback: true,
compress: true,
watchFiles: ['src/**/*'],
},
// Optimization
optimization: {
moduleIds: 'deterministic',
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
// Performance hints
performance: {
hints: isDev ? false : 'warning',
},
// Stats configuration
stats: isDev ? 'errors-warnings' : 'normal',
}; };
React Fast Refresh Boundaries
// src/components/ErrorBoundary.tsx 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;
} }
// Fast Refresh will reset error boundaries automatically
// src/App.tsx - Proper export for Fast Refresh import { useState } from 'react';
// Named export works with Fast Refresh export function App() { const [count, setCount] = useState(0);
return ( <div> <h1>Count: {count}</h1> <button onClick={() => setCount(c => c + 1)}>Increment</button> </div> ); }
// Don't mix component and non-component exports in same file // This breaks Fast Refresh: // export const API_URL = 'https://api.example.com';
CSS HMR Optimization
// vite.config.ts - CSS optimization export default defineConfig({ css: { // Enable CSS source maps devSourcemap: true,
// PostCSS configuration
postcss: {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
],
},
// CSS Modules
modules: {
localsConvention: 'camelCase',
generateScopedName: '[name]__[local]___[hash:base64:5]',
},
}, });
// tailwind.config.js - Optimize for HMR module.exports = { content: ['./src/**/*.{js,ts,jsx,tsx}'],
// JIT mode is default in v3+ // Reduces CSS generation time
// Disable unused features corePlugins: { preflight: true, // Disable unused utilities // float: false, // clear: false, }, };
Development Scripts
// package.json { "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
// vite.config.ts - Plugin for 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()], });
// src/dev-utils.ts - Client-side HMR monitoring 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"]
docker-compose.dev.yml
services: app: build: context: . dockerfile: Dockerfile.dev volumes: # Mount source for HMR - ./src:/app/src - ./public:/app/public # Exclude node_modules - /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:
-
Fast compiler (SWC/esbuild)
-
Dependency pre-bundling
-
Filesystem cache enabled
-
Source maps configured
-
Watch options optimized
-
Type checking separated
-
CSS HMR working
-
Error overlay configured
-
Docker polling setup
-
Performance monitoring