--- name: bundle-optimization description: Bundle size reduction strategies, icon optimization, tree-shaking, and performance analysis. Use when reducing bundle size or optimizing imports. --- # Bundle Optimization Skill ## Overview This project achieved a **90% bundle reduction** (2.33MB → 236KB) through strategic optimizations. This skill documents proven patterns for maintaining optimal bundle size. ## Key Achievement: Icon Optimization **Problem:** Wildcard imports caused massive bundle bloat **Solution:** Icon manifest with build-time generation **Result:** 90% reduction in bundle size ### The Problem ```typescript // ❌ WRONG - Imports entire simple-icons library (2.33MB) import * as Icons from 'simple-icons'; // This single line imports 2,000+ icons even if you only use 5 const reactIcon = Icons.siReact; ```typescript ### The Solution: Icon Manifest **Location:** `src/lib/icon-manifest.ts` ### How it works 1. Curate list of needed icons (42 in this project) 2. Build script generates manifest with only those icons 3. Runtime imports from manifest instead of full library ```typescript // ✅ CORRECT - Only includes curated icons import { getIcon } from '@/lib/icon-manifest'; const reactIcon = getIcon('react'); // Bundle only includes 42 icons instead of 2,000+ ```typescript ### Implementation **1. Icon Manifest Generator Script** **Location:** `scripts/generate-icons.js` ```javascript // Runs during build process // Reads curated icon list // Generates optimized manifest // Only includes icons actually used in project ```typescript **2. Icon Manifest** **Location:** `src/lib/icon-manifest.ts` ```typescript // Auto-generated during build import { siReact, siTypescript, siNextdotjs, // ... only 42 icons } from 'simple-icons'; const iconManifest = { react: siReact, typescript: siTypescript, nextdotjs: siNextdotjs, // ... }; export const getIcon = (name: string) => iconManifest[name]; ```typescript **3. Package.json Build Hook** ```json { "scripts": { "prebuild": "node scripts/generate-icons.js", "build": "next build" } } ```typescript ### Usage Pattern ```typescript // Import the manifest function import { getIcon } from '@/lib/icon-manifest'; function SkillIcon({ name }: { name: string }) { const icon = getIcon(name); if (!icon) return null; return (
); } ```typescript ### Adding New Icons 1. Add icon name to curated list in `scripts/generate-icons.js` 2. Run build or `node scripts/generate-icons.js` 3. Manifest automatically regenerates 4. Use with `getIcon('newIconName')` ## Tree-Shaking Strategies ### 1. Named Imports Only ```typescript // ✅ CORRECT - Tree-shakeable import { Button } from '@/components/ui/button'; import { Camera, Settings } from 'lucide-react'; // ❌ WRONG - Imports everything import * as Components from '@/components'; import * as Icons from 'lucide-react'; ```typescript ### 2. Lucide Icons Configuration **Location:** `next.config.ts` ```typescript modularizeImports: { 'lucide-react': { transform: 'lucide-react/dist/esm/icons/{{kebabCase member}}', skipDefaultConversion: true, }, } ```typescript **Effect:** Each Lucide icon imports only its file, not entire library ### 3. Dynamic Imports for Large Dependencies ```typescript // ✅ CORRECT - Load only when needed const HeavyChart = dynamic(() => import('@/components/heavy-chart'), { loading: () => , ssr: false, // Skip SSR if not needed }); // Use in component ```typescript ### 4. Code Splitting by Route Next.js automatically code-splits by route, but you can optimize: ```typescript // app/heavy-feature/page.tsx import dynamic from 'next/dynamic'; // Split heavy components const HeavyFeature = dynamic(() => import('@/components/heavy-feature')); export default function Page() { return ; } ```typescript ## Bundle Analysis Workflow ### Step 1: Enable Bundle Analyzer ```bash # Install analyzer npm install --save-dev @next/bundle-analyzer # Run analysis ANALYZE=true npm run build ```typescript ### Step 2: Review Results Analyzer opens in browser showing: - Chunk sizes - Import paths - Duplicate dependencies - Optimization opportunities ### Step 3: Identify Issues ### Look for - Large chunks (>500KB) - Duplicate libraries (same lib in multiple chunks) - Unused imports - Large dependencies that could be lazy-loaded ### Step 4: Optimize ### Common fixes 1. Convert wildcard imports to named imports 2. Add dynamic imports for heavy components 3. Remove unused dependencies from package.json 4. Use smaller alternatives (e.g., date-fns instead of moment) ## Size Limit Configuration **Location:** `package.json` ```json { "size-limit": [ { "path": ".next/static/chunks/app/page.js", "limit": "40 KB", "name": "Homepage" }, { "path": ".next/static/chunks/app/skills/page.js", "limit": "40 KB", "name": "Skills Page" } ] } ```typescript ### Running Size Checks ```bash # Check all size limits npm run size # Output shows: # ✓ Homepage: 7.73 KB / 40 KB (within limit) # ✓ Skills: 6.31 KB / 40 KB (within limit) ```typescript ### CI/CD Integration Size limits run in GitHub Actions quality gates: ```yaml - name: Check bundle size run: npm run size ```typescript **Failure:** Blocks PR if any bundle exceeds limit ## Performance Optimization Patterns ### 1. Image Optimization ```typescript // ✅ CORRECT - Next.js Image component import Image from 'next/image'; Description ```typescript ### Benefits - Automatic WebP/AVIF conversion - Lazy loading by default - Responsive images - Optimized serving ### 2. Font Optimization ```typescript // app/layout.tsx import { Inter } from 'next/font/google'; const inter = Inter({ subsets: ['latin'], display: 'swap', preload: true, }); export default function RootLayout({ children }) { return ( {children} ); } ```typescript ### Benefits - Self-hosted fonts (no external requests) - Automatic font optimization - Zero layout shift ### 3. Script Loading Strategies ```typescript import Script from 'next/script'; // Load after page is interactive