Bundle optimization patterns — barrel file avoidance, conditional/dynamic imports, deferred third-party scripts, SVG precision. Use when reducing bundle size, improving LCP/TTI, or code-splitting.
Patterns to reduce initial bundle size, improve loading performance, and enable effective code-splitting.
Import directly from source files instead of barrel (index) files to avoid loading thousands of unused modules.
Bad (imports entire library):
import { Check, X, Menu } from 'lucide-react';
// Loads 1,583 modules, takes ~2.8s extra in dev
import { Button, TextField } from '@mui/material';
// Loads 2,225 modules
Good (direct imports):
import Check from 'lucide-react/dist/esm/icons/check';
import X from 'lucide-react/dist/esm/icons/x';
import Menu from 'lucide-react/dist/esm/icons/menu';
import Button from '@mui/material/Button';
import TextField from '@mui/material/TextField';
// next.config.js
module.exports = {
experimental: {
optimizePackageImports: ['lucide-react', '@mui/material'],
},
};
Commonly affected: lucide-react, @mui/material, @mui/icons-material, @tabler/icons-react, react-icons, lodash, date-fns, rxjs.
Reference: How we optimized package imports in Next.js
Use next/dynamic to lazy-load large components not needed on initial render.
Bad (Monaco bundles with main chunk ~300KB):
import { MonacoEditor } from './monaco-editor';
Good (loads on demand):
import dynamic from 'next/dynamic';
const MonacoEditor = dynamic(
() => import('./monaco-editor').then((m) => m.MonacoEditor),
{ ssr: false },
);
Load large data/modules only when a feature is activated.
function AnimationPlayer({ enabled, setEnabled }: Props) {
const [frames, setFrames] = useState<Frame[] | null>(null);
useEffect(() => {
if (enabled && !frames && typeof window !== 'undefined') {
import('./animation-frames.js')
.then((mod) => setFrames(mod.frames))
.catch(() => setEnabled(false));
}
}, [enabled, frames, setEnabled]);
if (!frames) return <Skeleton />;
return <Canvas frames={frames} />;
}
The typeof window !== 'undefined' check prevents bundling for SSR.
Analytics, logging, and error tracking don't block user interaction. Load after hydration.
Bad (blocks initial bundle):
import { Analytics } from '@vercel/analytics/react';
Good (loads after hydration):
import dynamic from 'next/dynamic';
const Analytics = dynamic(
() => import('@vercel/analytics/react').then((m) => m.Analytics),
{ ssr: false },
);
Reduce SVG coordinate precision to decrease file size.
Bad: <path d="M 10.293847 20.847362 L 30.938472 40.192837" />
Good: <path d="M 10.3 20.8 L 30.9 40.2" />
Automate: npx svgo --precision=1 --multipass icon.svg