Development Workflow Implementation Tools
1. Vite Hot Module Replacement
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 Proxy
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. GitHub Actions CI CD Pipeline
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
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. Storybook Component Documentation
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 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 }}