Development Workflow and Build Tools
1. Vite Hot Module Replacement (HMR) and Fast Refresh
Lightning-fast HMR with instant updates without full page reload, preserving application state during development.
| Feature | Description | Benefit | Use Case |
|---|---|---|---|
| Fast Refresh | Updates React components without losing state | Instant feedback | Component development |
| CSS HMR | Update styles without reload | Live style changes | UI/UX development |
| import.meta.hot API | Fine-grained HMR control | Custom module updates | Advanced scenarios |
| Dependency Pre-bundling | ESBuild pre-bundles node_modules | Fast cold start | Large dependencies |
Example: Vite configuration with HMR
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
// Enable Fast Refresh
fastRefresh: true,
// Babel options for custom transforms
babel: {
plugins: ['babel-plugin-styled-components']
}
})
],
server: {
port: 3000,
open: true,
hmr: {
// HMR configuration
overlay: true, // Show errors in browser overlay
port: 3000
},
// Watch options
watch: {
usePolling: false, // Use native file watchers
ignored: ['**/node_modules/**', '**/.git/**']
}
},
// Optimize dependencies
optimizeDeps: {
include: ['react', 'react-dom'],
exclude: ['@your-lib/package']
}
});
// Custom HMR handling in module
// utils/config.ts
export const config = {
apiUrl: 'http://localhost:8000',
timeout: 5000
};
// Accept HMR updates for this module
if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
console.log('Config updated:', newModule);
});
}
Example: React Fast Refresh with state preservation
// Counter.tsx - State is preserved during HMR
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(c => c + 1)}>
Increment
</button>
{/* Change this text - count state is preserved! */}
<p>Click the button to count</p>
</div>
);
}
// HMR Boundary - Force reload on certain changes
// AppProvider.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
export function AppProvider({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
);
}
// Force full reload if this module changes
if (import.meta.hot) {
import.meta.hot.dispose(() => {
// Cleanup before reload
queryClient.clear();
});
}
Example: Vite environment variables with HMR
// .env.development
VITE_API_URL=http://localhost:8000
VITE_APP_NAME=My App Dev
// Access in code (HMR updates automatically)
export const config = {
apiUrl: import.meta.env.VITE_API_URL,
appName: import.meta.env.VITE_APP_NAME,
isDev: import.meta.env.DEV,
isProd: import.meta.env.PROD
};
// TypeScript types for env variables
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_APP_NAME: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
// Conditional logic with HMR
if (import.meta.env.DEV) {
console.log('Development mode - HMR enabled');
}
Performance: Vite HMR is 10-100x faster than Webpack due to native ES modules and esbuild.
Cold start in ~500ms, updates in <50ms. Use
vite-plugin-inspect to debug HMR issues.
2. Webpack Dev Server and Proxy Configuration
Configure development proxy to bypass CORS issues and route API requests to backend server during local development.
| Proxy Type | Configuration | Use Case | Benefits |
|---|---|---|---|
| Simple Proxy | String target URL | Single backend API | Quick setup, bypass CORS |
| Path Rewrite | Modify request paths | Different API versioning | Flexible routing |
| Multiple Proxies | Object with multiple targets | Microservices | Route to different services |
| WebSocket Proxy | ws: true option | Real-time connections | WebSocket support |
Example: Webpack Dev Server proxy configuration
// webpack.config.js
module.exports = {
devServer: {
port: 3000,
// Simple proxy - forward /api to backend
proxy: {
'/api': 'http://localhost:8000'
}
}
};
// Request: http://localhost:3000/api/users
// Proxied to: http://localhost:8000/api/users
// Advanced proxy with options
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8000',
pathRewrite: { '^/api': '' }, // Remove /api prefix
changeOrigin: true, // Change origin header
secure: false, // Accept self-signed certificates
logLevel: 'debug' // Log proxy requests
},
// Multiple backend services
'/auth': {
target: 'http://localhost:8001',
changeOrigin: true
},
'/graphql': {
target: 'http://localhost:8002',
changeOrigin: true
},
// WebSocket proxy
'/ws': {
target: 'ws://localhost:8003',
ws: true, // Enable WebSocket proxying
changeOrigin: true
}
}
}
};
// Request: http://localhost:3000/api/users
// Proxied to: http://localhost:8000/users (api removed)
Example: Vite proxy configuration
// vite.config.ts
import { defineConfig } from 'vite';
export default defineConfig({
server: {
proxy: {
// Simple proxy
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
},
// Proxy with custom headers
'/auth': {
target: 'http://localhost:8001',
changeOrigin: true,
configure: (proxy, options) => {
proxy.on('proxyReq', (proxyReq, req, res) => {
// Add custom headers
proxyReq.setHeader('X-Custom-Header', 'value');
console.log('Proxying:', req.method, req.url);
});
proxy.on('proxyRes', (proxyRes, req, res) => {
console.log('Response:', proxyRes.statusCode, req.url);
});
}
},
// Conditional proxy based on request
'^/api/v[0-9]+': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: (path) => {
// /api/v1/users -> /v1/users
return path.replace(/^\/api/, '');
}
}
}
}
});
// Next.js proxy (next.config.js)
module.exports = {
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'http://localhost:8000/:path*'
}
];
}
};
Example: Create React App proxy setup
// package.json - Simple proxy
{
"proxy": "http://localhost:8000"
}
// All requests to unknown routes are proxied to backend
// src/setupProxy.js - Advanced proxy
const { createProxyMiddleware } = require('http-proxy-middleware');
module.exports = function(app) {
// API proxy
app.use(
'/api',
createProxyMiddleware({
target: 'http://localhost:8000',
changeOrigin: true,
pathRewrite: {
'^/api': '' // Remove /api prefix
},
onProxyReq: (proxyReq, req, res) => {
console.log('Proxying:', req.method, req.url);
}
})
);
// Auth service proxy
app.use(
'/auth',
createProxyMiddleware({
target: 'http://localhost:8001',
changeOrigin: true
})
);
// WebSocket proxy
app.use(
'/ws',
createProxyMiddleware({
target: 'http://localhost:8003',
ws: true,
changeOrigin: true
})
);
};
Security Note: Proxy configuration only works in development. In production, configure proper
CORS headers on backend or use same-origin deployment. Never expose sensitive backend URLs in client code.
3. CI/CD with GitHub Actions (Workflows, Pipelines)
Automate testing, building, and deployment with GitHub Actions for continuous integration and delivery.
| Workflow Stage | Actions | Purpose | Triggers |
|---|---|---|---|
| CI (Continuous Integration) | Lint, test, type-check, build | Validate code quality | Push, PR |
| CD (Continuous Deployment) | Build, deploy to staging/prod | Automated deployment | Merge to main |
| Scheduled Jobs | Security audits, dependency updates | Maintenance tasks | Cron schedule |
| Release | Version bump, changelog, publish | Package release | Tag creation |
Example: Complete CI/CD workflow for React app
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
lint-and-test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Lint
run: npm run lint
- name: Type check
run: npm run type-check
- name: Run tests
run: npm run test:ci
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/coverage-final.json
fail_ci_if_error: true
build:
runs-on: ubuntu-latest
needs: lint-and-test
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20.x'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
NODE_ENV: production
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build-output
path: dist/
retention-days: 7
Example: Deployment workflow to Vercel/Netlify
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
VITE_API_URL: ${{ secrets.PROD_API_URL }}
VITE_APP_NAME: 'Production App'
# Deploy to Vercel
- name: Deploy to Vercel
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
# Or deploy to Netlify
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v2
with:
publish-dir: './dist'
production-deploy: true
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
- name: Notify Slack
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Deployment to production completed'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
if: always()
Example: Monorepo CI with Turborepo
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # For turbo to detect changes
- uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Get pnpm store directory
id: pnpm-cache
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm turbo run build
- name: Test
run: pnpm turbo run test
- name: Lint
run: pnpm turbo run lint
Example: Automated dependency updates with Dependabot
# .github/dependabot.yml
version: 2
updates:
# Enable npm dependency updates
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
reviewers:
- "team-frontend"
labels:
- "dependencies"
- "automated"
# Group minor/patch updates
groups:
production-dependencies:
patterns:
- "*"
update-types:
- "minor"
- "patch"
# GitHub Actions updates
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
# Auto-merge Dependabot PRs (workflow)
# .github/workflows/dependabot-auto-merge.yml
name: Dependabot auto-merge
on: pull_request
permissions:
pull-requests: write
contents: write
jobs:
dependabot:
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
steps:
- name: Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v1
- name: Enable auto-merge for Dependabot PRs
if: steps.metadata.outputs.update-type == 'version-update:semver-patch'
run: gh pr merge --auto --squash "$PR_URL"
env:
PR_URL: ${{github.event.pull_request.html_url}}
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
Best Practice: Cache node_modules and build outputs for faster CI. Run jobs in parallel when
possible. Use matrix strategy for multi-version testing. Set up branch protection rules requiring CI pass.
4. Pre-commit Hooks (lint-staged, Husky, commitlint)
Run linters and formatters only on staged files before commit to catch issues early and maintain code quality.
| Tool | Purpose | When Runs | Performance |
|---|---|---|---|
| Husky | Git hooks management | Various git events | Instant hook registration |
| lint-staged | Run commands on staged files only | Pre-commit | Fast (only changed files) |
| commitlint | Validate commit messages | Commit-msg hook | Instant validation |
| pre-push | Run tests before push | Pre-push | Can be slow (full tests) |
Example: Complete pre-commit hooks setup
// Installation
npm install -D husky lint-staged @commitlint/cli @commitlint/config-conventional
// Initialize Husky
npx husky init
// package.json
{
"scripts": {
"prepare": "husky",
"lint": "eslint .",
"format": "prettier --write .",
"type-check": "tsc --noEmit",
"test": "vitest"
},
"lint-staged": {
"*.{ts,tsx,js,jsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md,css,scss}": [
"prettier --write"
],
"*.{ts,tsx}": [
() => "tsc --noEmit" // Type check all files, not just staged
]
}
}
// .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
// .husky/commit-msg
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no -- commitlint --edit $1
// .husky/pre-push
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run type-check
npm run test -- --run
// commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat', // New feature
'fix', // Bug fix
'docs', // Documentation
'style', // Formatting
'refactor', // Code restructuring
'test', // Tests
'chore', // Maintenance
'perf', // Performance
'ci', // CI/CD
'build', // Build system
'revert' // Revert commit
]
],
'subject-case': [2, 'always', 'sentence-case']
}
};
// Valid commit messages:
// ✅ feat: add user authentication
// ✅ fix: resolve navigation bug
// ✅ docs: update README installation steps
// ❌ added new feature (missing type)
// ❌ Fix: bug (wrong case)
Example: Advanced lint-staged configuration
// .lintstagedrc.js - More control
module.exports = {
// TypeScript/JavaScript files
'*.{ts,tsx}': [
'eslint --fix --max-warnings=0',
'prettier --write',
() => 'tsc --noEmit --pretty' // Run for all files
],
// JavaScript files
'*.{js,jsx}': [
'eslint --fix --max-warnings=0',
'prettier --write'
],
// Styles
'*.{css,scss}': [
'stylelint --fix',
'prettier --write'
],
// JSON/Markdown
'*.{json,md}': ['prettier --write'],
// Test files - run related tests
'**/*.test.{ts,tsx}': [
(filenames) => {
const tests = filenames.map(f => `--testPathPattern=${f}`).join(' ');
return `vitest run ${tests}`;
}
],
// Package.json - validate and sort
'package.json': [
'sort-package-json',
'prettier --write'
]
};
// Skip hooks when needed
// git commit --no-verify -m "emergency fix"
// git push --no-verify
Example: Monorepo lint-staged with Turborepo
// Root .lintstagedrc.js
module.exports = {
'*': 'prettier --write --ignore-unknown',
'*.{ts,tsx,js,jsx}': (filenames) => {
// Only lint files in affected packages
const packages = new Set(
filenames.map(f => f.split('/')[1]) // Extract package name
);
return Array.from(packages).map(
pkg => `turbo run lint --filter=./${pkg}`
);
}
};
// Per-package hooks
// apps/web/.lintstagedrc.js
module.exports = {
'*.{ts,tsx}': [
'eslint --fix',
'prettier --write',
() => 'tsc --noEmit -p apps/web'
]
};
// Root package.json
{
"scripts": {
"prepare": "husky"
},
"lint-staged": {
"apps/**/*.{ts,tsx}": [
"turbo run lint --filter=./apps/*"
],
"packages/**/*.{ts,tsx}": [
"turbo run lint --filter=./packages/*"
]
}
}
Performance Tip: Avoid running full test suite in pre-commit (too slow). Use pre-push for
tests.
For type-checking, use
() => 'tsc --noEmit' to check all files, not individual staged files.
5. Component Documentation with Storybook (MDX, Docs)
Develop, document, and test UI components in isolation with comprehensive documentation and interactive examples.
| Feature | Purpose | Benefit | Add-on |
|---|---|---|---|
| Component Isolation | Develop without full app | Faster iteration | Built-in |
| Interactive Docs | Auto-generate documentation | Living style guide | @storybook/addon-docs |
| Controls | Modify props in UI | Test all states | @storybook/addon-controls |
| Actions | Log event handlers | Debug interactions | @storybook/addon-actions |
| Accessibility | A11y testing | Catch issues early | @storybook/addon-a11y |
Example: Storybook setup and configuration
// Installation
npx storybook@latest init
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-a11y',
'@storybook/addon-coverage'
],
framework: {
name: '@storybook/react-vite',
options: {}
},
docs: {
autodocs: 'tag' // Auto-generate docs page
}
};
export default config;
// .storybook/preview.ts
import type { Preview } from '@storybook/react';
import '../src/styles/globals.css';
const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i
}
},
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#1a1a1a' }
]
}
}
};
export default preview;
Example: Component stories with all variants
// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'danger']
},
size: {
control: 'radio',
options: ['sm', 'md', 'lg']
},
onClick: { action: 'clicked' }
}
};
export default meta;
type Story = StoryObj<typeof Button>;
// Default story
export const Primary: Story = {
args: {
variant: 'primary',
children: 'Button'
}
};
// Variants
export const Secondary: Story = {
args: {
variant: 'secondary',
children: 'Button'
}
};
export const Danger: Story = {
args: {
variant: 'danger',
children: 'Delete'
}
};
// Sizes
export const Small: Story = {
args: {
size: 'sm',
children: 'Small Button'
}
};
export const Large: Story = {
args: {
size: 'lg',
children: 'Large Button'
}
};
// States
export const Disabled: Story = {
args: {
disabled: true,
children: 'Disabled'
}
};
export const Loading: Story = {
args: {
loading: true,
children: 'Loading...'
}
};
// With icons
export const WithIcon: Story = {
args: {
children: (
<>
<span>🚀</span> Launch
</>
)
}
};
Example: Interactive stories with play function
// LoginForm.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { within, userEvent, expect } from '@storybook/test';
import { LoginForm } from './LoginForm';
const meta: Meta<typeof LoginForm> = {
title: 'Features/LoginForm',
component: LoginForm,
parameters: {
layout: 'centered'
}
};
export default meta;
type Story = StoryObj<typeof LoginForm>;
// Basic story
export const Default: Story = {};
// Interactive test
export const FilledForm: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Find and fill inputs
const emailInput = canvas.getByLabelText('Email');
const passwordInput = canvas.getByLabelText('Password');
const submitButton = canvas.getByRole('button', { name: /sign in/i });
// Simulate user interaction
await userEvent.type(emailInput, 'user@example.com');
await userEvent.type(passwordInput, 'password123');
// Verify inputs
await expect(emailInput).toHaveValue('user@example.com');
await expect(passwordInput).toHaveValue('password123');
// Submit form
await userEvent.click(submitButton);
}
};
// Error state
export const WithErrors: Story = {
args: {
errors: {
email: 'Invalid email format',
password: 'Password is required'
}
}
};
Example: MDX documentation with embedded stories
<!-- Button.stories.mdx -->
import { Canvas, Meta, Story, ArgTypes } from '@storybook/blocks';
import { Button } from './Button';
import * as ButtonStories from './Button.stories';
<Meta of={ButtonStories} />
# Button Component
A versatile button component with multiple variants and sizes.
## Usage
```tsx
import { Button } from '@/components/Button';
function App() {
return (
<Button variant="primary" onClick={() => alert('Clicked!')}>
Click Me
</Button>
);
}
```
## Variants
<Canvas of={ButtonStories.Primary} />
<Canvas of={ButtonStories.Secondary} />
<Canvas of={ButtonStories.Danger} />
## Props
<ArgTypes of={Button} />
## Accessibility
- Keyboard navigable with Tab
- Supports aria-label and aria-describedby
- Disabled state properly communicated to screen readers
## Best Practices
- Use semantic button text (avoid "Click here")
- Provide loading state feedback
- Use appropriate variant for action importance
Storybook Benefits: Component catalog, visual testing with Chromatic, interaction testing,
accessibility auditing, responsive design testing, and living documentation for design systems.
6. Bundle Analyzer (webpack-bundle-analyzer) and Performance Monitoring
Analyze bundle size, identify large dependencies, and optimize JavaScript bundles for better performance.
| Tool | Platform | Features | Best For |
|---|---|---|---|
| webpack-bundle-analyzer | Webpack | Interactive treemap, module sizes | Webpack projects |
| rollup-plugin-visualizer | Vite/Rollup | Multiple chart types, treemap | Vite projects |
| source-map-explorer | Any bundler | Source map analysis | Generic analysis |
| bundlephobia | Web service | npm package size lookup | Dependency evaluation |
Example: Webpack Bundle Analyzer setup
// Installation
npm install -D webpack-bundle-analyzer
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: process.env.ANALYZE ? 'server' : 'disabled',
openAnalyzer: true,
generateStatsFile: true,
statsFilename: 'bundle-stats.json'
})
]
};
// package.json
{
"scripts": {
"build": "webpack --mode production",
"build:analyze": "ANALYZE=true webpack --mode production"
}
}
// Run analysis
npm run build:analyze
// Create React App
npm install -D webpack-bundle-analyzer
npm run build
npx webpack-bundle-analyzer build/static/js/*.js
Example: Vite bundle analysis with visualizer
// Installation
npm install -D rollup-plugin-visualizer
// vite.config.ts
import { defineConfig } from 'vite';
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
plugins: [
visualizer({
filename: './dist/stats.html',
open: true,
gzipSize: true,
brotliSize: true,
template: 'treemap' // or 'sunburst', 'network'
})
],
build: {
rollupOptions: {
output: {
manualChunks: {
'vendor-react': ['react', 'react-dom'],
'vendor-ui': ['@mui/material', '@emotion/react'],
'vendor-utils': ['lodash-es', 'date-fns']
}
}
}
}
});
// package.json
{
"scripts": {
"build": "vite build",
"build:analyze": "vite build --mode production"
}
}
// Output: dist/stats.html with interactive visualization
Example: Next.js bundle analysis
// Installation
npm install -D @next/bundle-analyzer
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true'
});
module.exports = withBundleAnalyzer({
// Next.js config
reactStrictMode: true,
swcMinify: true,
// Optimize bundle
webpack: (config, { isServer }) => {
if (!isServer) {
// Client-side optimizations
config.optimization.splitChunks = {
chunks: 'all',
cacheGroups: {
default: false,
vendors: false,
// Vendor chunk
vendor: {
name: 'vendor',
chunks: 'all',
test: /node_modules/
},
// Common chunks
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 10,
reuseExistingChunk: true,
enforce: true
}
}
};
}
return config;
}
});
// package.json
{
"scripts": {
"build": "next build",
"analyze": "ANALYZE=true next build"
}
}
// Run: npm run analyze
// Opens two browser tabs: client and server bundles
Example: Bundle size budgets and monitoring
// webpack.config.js - Size budgets
module.exports = {
performance: {
maxAssetSize: 250000, // 250kb
maxEntrypointSize: 400000, // 400kb
hints: 'error', // or 'warning'
assetFilter: (assetFilename) => {
return assetFilename.endsWith('.js');
}
}
};
// vite.config.ts - Size warnings
export default defineConfig({
build: {
chunkSizeWarningLimit: 500, // 500kb warning threshold
rollupOptions: {
output: {
manualChunks(id) {
// Split large dependencies
if (id.includes('node_modules')) {
if (id.includes('react')) return 'vendor-react';
if (id.includes('@mui')) return 'vendor-ui';
if (id.includes('lodash')) return 'vendor-utils';
return 'vendor';
}
}
}
}
}
});
// package.json - Bundle size tracking
{
"scripts": {
"size": "size-limit",
"size:why": "size-limit --why"
},
"size-limit": [
{
"path": "dist/index.js",
"limit": "200 KB"
},
{
"path": "dist/vendor.js",
"limit": "300 KB"
}
]
}
// .github/workflows/size.yml - CI size check
name: Size Check
on: pull_request
jobs:
size:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}