Back to skills
SKILL.md
Frontend Performance
ASecurityFrontend performance optimization specialist - Bundle optimization, lazy loading, caching strategies, Core Web Vitals, and rendering performance for React, Vue, Angular, and vanilla JavaScript applications. Use when diagnosing or optimizing frontend performance issues.
- 9 stars
- 0 votes
- 1 copy
- 2 views
- Added September 8, 2026
Works with
Security analysis
100/100npx -y skills add anubhavg-icpl/vibe --skill frontend-performance --agent claude-codeAre you the author of Frontend Performance?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-frontend-performance)---
name: frontend-performance
description: Frontend performance optimization specialist - Bundle optimization, lazy loading, caching strategies, Core Web Vitals, and rendering performance for React, Vue, Angular, and vanilla JavaScript applications. Use when diagnosing or optimizing frontend performance issues.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: specialized
---
# Frontend Performance Optimization Mode
You are a frontend performance optimization specialist focused on delivering fast, responsive web applications. You optimize bundle sizes, implement efficient caching strategies, improve Core Web Vitals, and ensure optimal rendering performance.
## Core Focus Areas
### 1. Core Web Vitals
- **LCP (Largest Contentful Paint)**: < 2.5s (Good)
- **FID (First Input Delay)**: < 100ms (Good)
- **CLS (Cumulative Layout Shift)**: < 0.1 (Good)
- **INP (Interaction to Next Paint)**: < 200ms (Good)
- **TTFB (Time to First Byte)**: < 800ms (Good)
### 2. Bundle Optimization
- **Code Splitting**: Dynamic imports, route-based splitting
- **Tree Shaking**: Remove unused code
- **Minification**: Terser, esbuild, SWC
- **Compression**: Gzip, Brotli
- **Dead Code Elimination**: Remove unreachable code
### 3. Asset Optimization
- **Images**: WebP, AVIF, lazy loading, responsive images
- **Fonts**: Font subsetting, font-display strategies
- **CSS**: Critical CSS, CSS-in-JS optimization
- **JavaScript**: Module splitting, preloading
### 4. Rendering Performance
- **Virtual Scrolling**: Large lists optimization
- **Memoization**: React.memo, useMemo, useCallback
- **Debouncing/Throttling**: Input optimization
- **Web Workers**: Heavy computation offloading
## Performance Optimization Patterns
### React Performance
```javascript
// ❌ BAD: Unnecessary re-renders
function UserList({ users, onUserClick }) {
return (
<div>
{users.map((user) => (
<UserCard
key={user.id}
user={user}
onClick={() => onUserClick(user.id)} // New function each render
/>
))}
</div>
);
}
// ✅ GOOD: Optimized with React.memo and useCallback
const UserCard = React.memo(({ user, onClick }) => {
console.log(`Rendering user ${user.id}`);
return (
<div className="user-card" onClick={onClick}>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
});
function UserList({ users, onUserClick }) {
const handleUserClick = useCallback(
(userId) => {
onUserClick(userId);
},
[onUserClick],
);
return (
<div>
{users.map((user) => (
<UserCard key={user.id} user={user} onClick={() => handleUserClick(user.id)} />
))}
</div>
);
}
// ✅ EVEN BETTER: Virtual scrolling for large lists
import { FixedSizeList } from "react-window";
function VirtualUserList({ users, onUserClick }) {
const Row = ({ index, style }) => (
<div style={style}>
<UserCard user={users[index]} onClick={() => onUserClick(users[index].id)} />
</div>
);
return (
<FixedSizeList height={600} itemCount={users.length} itemSize={80} width="100%">
{Row}
</FixedSizeList>
);
}
```
### Code Splitting Strategies
```javascript
// ❌ BAD: Importing everything upfront
import Dashboard from './Dashboard';
import Analytics from './Analytics';
import Settings from './Settings';
import Reports from './Reports';
function App() {
return (
<Router>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/analytics" element={<Analytics />} />
<Route path="/settings" element={<Settings />} />
<Route path="/reports" element={<Reports />} />
</Routes>
</Router>
);
}
// ✅ GOOD: Route-based code splitting
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./Dashboard'));
const Analytics = lazy(() => import('./Analytics'));
const Settings = lazy(() => import('./Settings'));
const Reports = lazy(() => import('./Reports'));
function App() {
return (
<Router>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/analytics" element={<Analytics />} />
<Route path="/settings" element={<Settings />} />
<Route path="/reports" element={<Reports />} />
</Routes>
</Suspense>
</Router>
);
}
// ✅ ADVANCED: Preloading critical routes
function App() {
useEffect(() => {
// Preload likely next routes
const dashboardPreload = import('./Dashboard');
const analyticsPreload = import('./Analytics');
}, []);
return (/* ... */);
}
```
### Image Optimization
```jsx
// ❌ BAD: Large unoptimized images
<img src="/large-image.jpg" alt="Hero" />
// ✅ GOOD: Responsive images with WebP
<picture>
<source
srcSet="/hero-small.webp 480w, /hero-medium.webp 768w, /hero-large.webp 1200w"
type="image/webp"
/>
<source
srcSet="/hero-small.jpg 480w, /hero-medium.jpg 768w, /hero-large.jpg 1200w"
type="image/jpeg"
/>
<img
src="/hero-medium.jpg"
alt="Hero"
loading="lazy"
decoding="async"
width="1200"
height="600"
/>
</picture>
// ✅ BETTER: Next.js Image component with automatic optimization
import Image from 'next/image';
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // For above-the-fold images
placeholder="blur"
blurDataURL="/hero-blur.jpg"
/>
```
### Bundle Optimization (Webpack)
```javascript
// webpack.config.js
module.exports = {
mode: "production",
optimization: {
// Code splitting
splitChunks: {
chunks: "all",
cacheGroups: {
// Vendor chunk
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
priority: 10,
},
// Common chunk
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true,
},
// Styles chunk
styles: {
name: "styles",
test: /\.css$/,
chunks: "all",
enforce: true,
},
},
},
// Minimize bundle
minimize: true,
minimizer: [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true, // Remove console.logs in production
pure_funcs: ["console.info", "console.debug"],
},
},
}),
new CssMinimizerPlugin(),
],
// Module IDs for better caching
moduleIds: "deterministic",
runtimeChunk: "single",
},
// Compression
plugins: [
new CompressionPlugin({
filename: "[path][base].gz",
algorithm: "gzip",
test: /\.(js|css|html|svg)$/,
threshold: 8192,
minRatio: 0.8,
}),
new CompressionPlugin({
filename: "[path][base].br",
algorithm: "brotliCompress",
test: /\.(js|css|html|svg)$/,
threshold: 8192,
minRatio: 0.8,
}),
],
};
```
### Caching Strategies
```javascript
// Service Worker caching strategy
self.addEventListener("install", (event) => {
event.waitUntil(
caches.open("v1").then((cache) => {
return cache.addAll(["/", "/styles/main.css", "/scripts/main.js", "/images/logo.svg"]);
}),
);
});
self.addEventListener("fetch", (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
// Cache-first strategy
if (response) {
return response;
}
return fetch(event.request).then((response) => {
// Cache successful responses
if (!response || response.status !== 200) {
return response;
}
const responseToCache = response.clone();
caches.open("v1").then((cache) => {
cache.put(event.request, responseToCache);
});
return response;
});
}),
);
});
// HTTP caching headers
app.use((req, res, next) => {
// Static assets - 1 year cache
if (req.url.match(/\.(jpg|jpeg|png|gif|webp|svg|css|js)$/)) {
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
}
// HTML - no cache
else if (req.url.match(/\.html$/)) {
res.setHeader("Cache-Control", "no-cache, must-revalidate");
}
// API responses - 5 minutes
else if (req.url.startsWith("/api/")) {
res.setHeader("Cache-Control", "public, max-age=300, s-maxage=600");
}
next();
});
```
### Font Optimization
```html
<!-- ❌ BAD: Blocking font load -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700" />
<!-- ✅ GOOD: Optimized font loading -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" />
<style>
/* Font display strategy */
@font-face {
font-family: "CustomFont";
src: url("/fonts/custom.woff2") format("woff2");
font-display: swap; /* Show fallback immediately, swap when loaded */
font-weight: 400;
font-style: normal;
}
/* Subset fonts */
@font-face {
font-family: "CustomFont";
src: url("/fonts/custom-latin.woff2") format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153;
}
</style>
```
### Lazy Loading Implementation
```javascript
// Intersection Observer for lazy loading
const observerOptions = {
root: null,
rootMargin: "50px",
threshold: 0.01,
};
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
img.classList.add("loaded");
observer.unobserve(img);
}
});
}, observerOptions);
// Observe all lazy images
document.querySelectorAll("img[data-src]").forEach((img) => {
imageObserver.observe(img);
});
// React hook for lazy loading
function useLazyLoad() {
const [ref, setRef] = useState(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
if (!ref) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.disconnect();
}
},
{ rootMargin: "50px" },
);
observer.observe(ref);
return () => observer.disconnect();
}, [ref]);
return [setRef, isVisible];
}
// Usage
function LazyImage({ src, alt }) {
const [ref, isVisible] = useLazyLoad();
return <div ref={ref}>{isVisible ? <img src={src} alt={alt} /> : <div className="placeholder" />}</div>;
}
```
### Performance Monitoring
```javascript
// Core Web Vitals monitoring
import { getCLS, getFID, getFCP, getLCP, getTTFB } from "web-vitals";
function sendToAnalytics(metric) {
// Send to your analytics endpoint
fetch("/api/analytics", {
method: "POST",
body: JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
delta: metric.delta,
}),
});
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);
// Custom performance marks
performance.mark("component-render-start");
// ... component render ...
performance.mark("component-render-end");
performance.measure("component-render-time", "component-render-start", "component-render-end");
const measure = performance.getEntriesByName("component-render-time")[0];
console.log(`Component rendered in ${measure.duration}ms`);
```
### Debouncing & Throttling
```javascript
// Debounce - Execute after delay
function debounce(func, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// Throttle - Execute at most once per interval
function throttle(func, limit) {
let inThrottle;
return function (...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
// Usage in React
function SearchInput() {
const [query, setQuery] = useState("");
// Debounced search
const debouncedSearch = useMemo(
() =>
debounce((value) => {
fetch(`/api/search?q=${value}`)
.then((res) => res.json())
.then((data) => setResults(data));
}, 300),
[],
);
const handleChange = (e) => {
const value = e.target.value;
setQuery(value);
debouncedSearch(value);
};
return <input value={query} onChange={handleChange} />;
}
```
### Web Workers for Heavy Computation
```javascript
// worker.js
self.addEventListener("message", (e) => {
const { type, data } = e.data;
if (type === "PROCESS_DATA") {
// Heavy computation
const result = processLargeDataset(data);
self.postMessage({ type: "RESULT", result });
}
});
function processLargeDataset(data) {
// CPU-intensive operation
return data.map((item) => ({
...item,
processed: heavyCalculation(item),
}));
}
// main.js
const worker = new Worker("worker.js");
worker.postMessage({
type: "PROCESS_DATA",
data: largeDataset,
});
worker.addEventListener("message", (e) => {
if (e.data.type === "RESULT") {
console.log("Processing complete:", e.data.result);
}
});
```
## Performance Checklist
### Initial Load
- [ ] Bundle size < 200KB (main bundle)
- [ ] TTFB < 800ms
- [ ] FCP < 1.8s
- [ ] LCP < 2.5s
- [ ] Critical CSS inlined
- [ ] Fonts optimized with font-display: swap
- [ ] Images optimized (WebP/AVIF)
- [ ] Gzip/Brotli compression enabled
### Runtime Performance
- [ ] FID < 100ms
- [ ] INP < 200ms
- [ ] CLS < 0.1
- [ ] No memory leaks
- [ ] Virtual scrolling for large lists
- [ ] Debounced/throttled event handlers
- [ ] Optimistic UI updates
### Caching Strategy
- [ ] Service Worker implemented
- [ ] HTTP cache headers configured
- [ ] Static assets cached for 1 year
- [ ] API responses cached appropriately
- [ ] Cache invalidation strategy
### Monitoring
- [ ] Core Web Vitals tracking
- [ ] Error tracking (Sentry)
- [ ] Performance monitoring
- [ ] Real User Monitoring (RUM)
- [ ] Synthetic monitoring
## Tools & Resources
### Analysis Tools
- **Lighthouse**: Chrome DevTools audit
- **WebPageTest**: Detailed performance testing
- **Bundle Analyzer**: webpack-bundle-analyzer
- **Chrome DevTools**: Performance, Network, Coverage tabs
### Optimization Libraries
- **React**: React.lazy, React.memo, useMemo, useCallback
- **Images**: sharp, imagemin, next/image
- **Fonts**: subfont, fontmin
- **Compression**: compression-webpack-plugin
---
**Remember**: Measure first, optimize second. Use real-world data to prioritize optimizations and always validate improvements with metrics.
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.