From performance-tools
Bundle analysis and optimization strategies for Next.js applications using Webpack and Turbopack. PROACTIVELY activate for: (1) Analyzing bundle size, (2) Implementing code splitting, (3) Configuring tree shaking, (4) Dynamic imports, (5) Webpack/Turbopack configuration. Triggers: "bundle size", "webpack", "turbopack", "code splitting", "tree shaking", "lazy loading", "bundle analyzer", "dynamic import"
How this skill is triggered — by the user, by Claude, or both
Slash command
/performance-tools:frontend-bundle-analysis-and-optimizationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill provides actionable strategies for analyzing and reducing JavaScript bundle size using Webpack or Turbopack, focusing on code splitting, tree shaking, and dependency analysis.
This skill provides actionable strategies for analyzing and reducing JavaScript bundle size using Webpack or Turbopack, focusing on code splitting, tree shaking, and dependency analysis.
Visual bundle analysis is the first step in any optimization effort. It shows which modules contribute most to bundle size.
npm install @next/bundle-analyzer --save-dev
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
})
module.exports = withBundleAnalyzer({
// Your Next.js config
})
ANALYZE=true npm run build
This opens an interactive treemap showing:
Dynamic imports enable code splitting, loading JavaScript only when needed.
// BAD: Loads heavy component immediately
import HeavyChart from '@/components/HeavyChart'
export default function Dashboard() {
return <HeavyChart data={data} />
}
// GOOD: Loads only when rendered
import dynamic from 'next/dynamic'
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
loading: () => <ChartSkeleton />,
ssr: false, // Disable SSR if component uses browser APIs
})
export default function Dashboard() {
return <HeavyChart data={data} />
}
// Load component only when needed
import dynamic from 'next/dynamic'
import { useState } from 'react'
const AdminPanel = dynamic(() => import('@/components/AdminPanel'))
export default function Dashboard({ isAdmin }: { isAdmin: boolean }) {
const [showAdmin, setShowAdmin] = useState(false)
return (
<div>
<h1>Dashboard</h1>
{isAdmin && (
<button onClick={() => setShowAdmin(true)}>
Show Admin Panel
</button>
)}
{/* Only loads when button is clicked */}
{showAdmin && <AdminPanel />}
</div>
)
}
// For named exports, use an object with 'default'
const DynamicComponent = dynamic(() =>
import('@/components/Hello').then((mod) => mod.Hello)
)
// BAD: Import entire library
import { ComponentA, ComponentB } from 'heavy-library'
// GOOD: Split into separate chunks
const ComponentA = dynamic(() => import('heavy-library/ComponentA'))
const ComponentB = dynamic(() => import('heavy-library/ComponentB'))
const HeavyEditor = dynamic(() => import('@/components/RichTextEditor'), {
loading: () => (
<div className="editor-skeleton">
<div className="toolbar-skeleton" />
<div className="content-skeleton" />
</div>
),
ssr: false,
})
Next.js automatically code-splits by route, but you can optimize further.
// app/dashboard/page.tsx
import dynamic from 'next/dynamic'
// Heavy components loaded only on this route
const Analytics = dynamic(() => import('@/components/Analytics'))
const UserTable = dynamic(() => import('@/components/UserTable'))
export default function DashboardPage() {
return (
<div>
<Analytics />
<UserTable />
</div>
)
}
// app/layout.tsx
// Common layout code shared across routes
export default function RootLayout({ children }) {
return (
<html>
<body>
<Header /> {/* Shared, in main bundle */}
{children} {/* Route-specific, code-split */}
<Footer /> {/* Shared, in main bundle */}
</body>
</html>
)
}
Fine-tune how code is divided into chunks for optimal caching and loading.
Next.js has sensible defaults, but you can customize:
// next.config.js
module.exports = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.optimization.splitChunks = {
chunks: 'all',
cacheGroups: {
default: false,
vendors: false,
// Vendor chunk for node_modules
vendor: {
name: 'vendor',
chunks: 'all',
test: /node_modules/,
priority: 20,
},
// Common chunk for shared code
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 10,
reuseExistingChunk: true,
enforce: true,
},
},
}
}
return config
},
}
// Separate React/Next.js into their own chunk for better caching
cacheGroups: {
framework: {
name: 'framework',
test: /[\\/]node_modules[\\/](react|react-dom|next)[\\/]/,
priority: 40,
enforce: true,
},
}
// Create separate chunk for a heavy library
cacheGroups: {
charts: {
name: 'charts',
test: /[\\/]node_modules[\\/](recharts|d3|chart\.js)[\\/]/,
priority: 30,
enforce: true,
},
}
splitChunks: {
chunks: 'all',
maxSize: 244 * 1024, // 244 KB max chunk size
minSize: 20 * 1024, // 20 KB min chunk size
}
Tree shaking eliminates unused code, but requires proper configuration.
{
"name": "my-library",
"sideEffects": false
}
This tells bundlers that no files have side effects, enabling aggressive tree shaking.
{
"sideEffects": [
"*.css",
"*.scss",
"./src/polyfills.ts"
]
}
// BAD: Imports entire library
import _ from 'lodash'
const result = _.debounce(fn, 100)
// GOOD: Import specific function
import debounce from 'lodash/debounce'
const result = debounce(fn, 100)
// EVEN BETTER: Use tree-shakeable alternative
import { debounce } from 'lodash-es'
const result = debounce(fn, 100)
// BAD: Entire date-fns library loaded
import * as dateFns from 'date-fns'
dateFns.format(new Date(), 'yyyy-MM-dd')
// GOOD: Only format function loaded
import { format } from 'date-fns'
format(new Date(), 'yyyy-MM-dd')
// utils/index.ts - BAD: Barrel file prevents tree shaking
export * from './stringUtils'
export * from './arrayUtils'
export * from './dateUtils'
// GOOD: Import directly from specific files
import { formatString } from '@/utils/stringUtils'
Turbopack is Next.js's Rust-based bundler, significantly faster than Webpack.
next dev --turbo
// next.config.js
module.exports = {
experimental: {
turbo: {},
},
}
next dev --turbo# Install dependency analyzer
npm install -g depcheck
# Find unused dependencies
depcheck
# Check bundle impact
npm install -g bundle-phobia-cli
bundle-phobia lodash
| Heavy Library | Lighter Alternative | Savings |
|---|---|---|
| moment.js (288 KB) | date-fns (27 KB) | 261 KB |
| lodash (72 KB) | lodash-es (tree-shakeable) | ~50 KB |
| axios (13 KB) | fetch API (native) | 13 KB |
| jquery (87 KB) | Native DOM APIs | 87 KB |
Optimize third-party scripts:
// BAD: Blocks rendering
<script src="https://www.googletagmanager.com/gtag/js" />
// GOOD: Optimized loading with next/third-parties
import { GoogleAnalytics } from '@next/third-parties/google'
export default function RootLayout({ children }) {
return (
<html>
<body>{children}</body>
<GoogleAnalytics gaId="G-XYZ" />
</html>
)
}
Set limits to prevent bundle size regression.
// next.config.js
module.exports = {
webpack: (config) => {
config.performance = {
maxAssetSize: 250 * 1024, // 250 KB
maxEntrypointSize: 400 * 1024, // 400 KB
hints: 'error', // Fail build if exceeded
}
return config
},
}
// package.json
{
"scripts": {
"build": "next build",
"analyze": "ANALYZE=true next build",
"check-bundle": "npm run build && bundlesize"
},
"bundlesize": [
{
"path": ".next/static/chunks/*.js",
"maxSize": "250 KB"
}
]
}
// BAD: Everything in one bundle
import HeavyChart from './HeavyChart'
import HeavyEditor from './HeavyEditor'
import HeavyMap from './HeavyMap'
export default function Page() {
return (
<>
<HeavyChart />
<HeavyEditor />
<HeavyMap />
</>
)
}
// BAD: 100+ KB
import _ from 'lodash'
// GOOD: ~5 KB
import debounce from 'lodash/debounce'
// BAD: Creates hundreds of tiny chunks
splitChunks: {
minSize: 1 * 1024, // 1 KB minimum
}
// GOOD: Reasonable chunk sizes
splitChunks: {
minSize: 20 * 1024, // 20 KB minimum
}
Build without ever checking bundle composition. Always use bundle analyzer periodically.
Multiple versions of the same package across chunks. Use npm dedupe or manage peer dependencies properly.
npx claudepluginhub agentient/vibekit --plugin performance-toolsReduces bundle size and optimizes runtime performance for Next.js production deployments using bundle analysis, dynamic imports, and code splitting.
Analyzes and reduces frontend bundle sizes using webpack-bundle-analyzer, rollup-plugin-visualizer, source-map-explorer. Guides tree-shaking, code splitting, lazy loading for large bundles.
Audits JavaScript bundle size to reduce parse/compile time and improve TTI. Covers tree-shaking, code-splitting, and budget enforcement with tools like webpack-bundle-analyzer.