| name | performance-optimizer |
| description | Optimize frontend performance with bundle size reduction, lazy loading, and Core Web Vitals improvements. Use when improving page speed, reducing bundle size, or optimizing Core Web Vitals. |
Performance Optimizer
Optimize frontend performance for faster load times and better user experience.
Quick Start
Measure with Lighthouse, optimize images, code split, lazy load, minimize bundle size, implement caching.
Instructions
Core Web Vitals
Largest Contentful Paint (LCP):
- Target: < 2.5s
- Measures: Loading performance
- Optimize: Images, fonts, server response
First Input Delay (FID):
- Target: < 100ms
- Measures: Interactivity
- Optimize: JavaScript execution, code splitting
Cumulative Layout Shift (CLS):
- Target: < 0.1
- Measures: Visual stability
- Optimize: Image dimensions, font loading
Bundle Size Optimization
Analyze bundle:
npm install --save-dev webpack-bundle-analyzer
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
plugins: [
new BundleAnalyzerPlugin()
]
npm install --save-dev rollup-plugin-visualizer
Code splitting:
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Profile = lazy(() => import('./pages/Profile'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
);
}
Component-based splitting:
const HeavyChart = lazy(() => import('./HeavyChart'));
function Dashboard() {
return (
<div>
<Suspense fallback={<ChartSkeleton />}>
<HeavyChart data={data} />
</Suspense>
</div>
);
}
Tree shaking:
import _ from 'lodash';
import debounce from 'lodash/debounce';
import { debounce } from 'lodash-es';
Image Optimization
Use Next.js Image:
import Image from 'next/image';
<Image
src="/hero.jpg"
alt="Hero image"
width={1200}
height={600}
priority // For above-fold images
placeholder="blur"
/>
Lazy load images:
<img
src="image.jpg"
alt="Description"
loading="lazy"
width="800"
height="600"
/>
Use modern formats:
<picture>
<source srcSet="image.avif" type="image/avif" />
<source srcSet="image.webp" type="image/webp" />
<img src="image.jpg" alt="Description" />
</picture>
Responsive images:
<img
srcSet="
image-320w.jpg 320w,
image-640w.jpg 640w,
image-1280w.jpg 1280w
"
sizes="(max-width: 640px) 100vw, 640px"
src="image-640w.jpg"
alt="Description"
/>
JavaScript Optimization
Minimize JavaScript:
npm run build
ls -lh dist/assets/*.js
Remove unused code:
export { specificFunction };
Defer non-critical JS:
<script src="analytics.js" defer></script>
<script src="non-critical.js" async></script>
CSS Optimization
Critical CSS:
<style dangerouslySetInnerHTML={{
__html: criticalCSS
}} />
<link
rel="preload"
href="/styles.css"
as="style"
onLoad="this.onload=null;this.rel='stylesheet'"
/>
Remove unused CSS:
npm install --save-dev @fullhuman/postcss-purgecss
CSS-in-JS optimization:
Lazy Loading
Intersection Observer:
function LazyComponent() {
const [isVisible, setIsVisible] = useState(false);
const ref = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
},
{ threshold: 0.1 }
);
if (ref.current) {
observer.observe(ref.current);
}
return () => observer.disconnect();
}, []);
return (
<div ref={ref}>
{isVisible ? <HeavyComponent /> : <Placeholder />}
</div>
);
}
React.lazy with retry:
function lazyWithRetry(componentImport) {
return lazy(() =>
componentImport().catch(() => {
return componentImport();
})
);
}
const Dashboard = lazyWithRetry(() => import('./Dashboard'));
Caching Strategies
Service Worker:
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll([
'/',
'/styles.css',
'/script.js',
]);
})
);
});
HTTP caching headers:
# Static assets (immutable)
Cache-Control: public, max-age=31536000, immutable
# HTML (revalidate)
Cache-Control: no-cache
# API responses
Cache-Control: private, max-age=300
React Query caching:
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000,
cacheTime: 10 * 60 * 1000,
},
},
});
Font Optimization
Font loading:
@font-face {
font-family: 'MyFont';
src: url('/fonts/myfont.woff2') format('woff2');
font-display: swap;
}
Preload fonts:
<link
rel="preload"
href="/fonts/myfont.woff2"
as="font"
type="font/woff2"
crossorigin
/>
Variable fonts:
@font-face {
font-family: 'Inter';
src: url('/fonts/inter-var.woff2') format('woff2');
font-weight: 100 900;
}
Rendering Optimization
Avoid layout thrashing:
element.style.width = element.offsetWidth + 10 + 'px';
element2.style.width = element2.offsetWidth + 10 + 'px';
const width1 = element.offsetWidth;
const width2 = element2.offsetWidth;
element.style.width = width1 + 10 + 'px';
element2.style.width = width2 + 10 + 'px';
Use CSS transforms:
.element {
left: 100px;
}
.element {
transform: translateX(100px);
}
Virtualize long lists:
import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualList({ items }) {
const parentRef = useRef();
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
{virtualizer.getVirtualItems().map(virtualItem => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualItem.start}px)`,
}}
>
{items[virtualItem.index].name}
</div>
))}
</div>
</div>
);
}
Performance Monitoring
Web Vitals:
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
function sendToAnalytics(metric) {
console.log(metric);
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);
Performance Observer:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.duration);
}
});
observer.observe({ entryTypes: ['measure', 'navigation'] });
Performance Checklist
Loading:
Rendering:
Caching:
Monitoring:
Best Practices
Performance budgets:
{
"budgets": [{
"resourceSizes": [{
"resourceType": "script",
"budget": 200
}, {
"resourceType": "image",
"budget": 500
}]
}]
}
Lighthouse CI:
{
"ci": {
"assert": {
"assertions": {
"categories:performance": ["error", {"minScore": 0.9}],
"first-contentful-paint": ["error", {"maxNumericValue": 2000}]
}
}
}
}
Regular audits:
- Weekly: Check bundle size
- Monthly: Full Lighthouse audit
- Quarterly: Performance review