| name | Web Performance |
| description | This skill activates when users discuss site speed, Core Web Vitals, optimization, bundle size, lazy loading, or performance issues. Provides guidance on measuring, analyzing, and optimizing web performance with focus on LCP, FID, CLS, and modern performance patterns. |
Web Performance Optimization
Optimize web applications for speed, Core Web Vitals, and user experience.
When This Activates
- "slow site", "performance", "optimization", "speed"
- "Core Web Vitals", "LCP", "FID", "CLS", "Lighthouse"
- "bundle size", "lazy loading", "code splitting"
- User reports poor performance scores
- Discussing how to improve site speed
Core Web Vitals (Google's Metrics)
1. Largest Contentful Paint (LCP)
Target: < 2.5 seconds
What it measures: Time until largest content element renders
Common causes of poor LCP:
- Slow server response time
- Render-blocking JavaScript/CSS
- Large images without optimization
- Client-side rendering
How to fix:
import Image from 'next/image';
<Image
src="/jewelry/ring.jpg"
alt="Luxury Ring"
width={800}
height={800}
priority // Load immediately for above-fold
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>
<link
rel="preload"
href="/fonts/archivo.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
2. First Input Delay (FID) / Interaction to Next Paint (INP)
Target: < 100ms (FID) or < 200ms (INP)
What it measures: Time from user interaction to browser response
Common causes:
- Long-running JavaScript
- Heavy JavaScript execution
- Large bundle blocking main thread
How to fix:
<script src="analytics.js" defer></script>
self.addEventListener('message', (e) => {
const result = expensiveCalculation(e.data);
self.postMessage(result);
});
const worker = new Worker('/worker.js');
worker.postMessage(inputData);
worker.onmessage = (e) => {
console.log('Result:', e.data);
};
async function processLargeDataset(items: any[]) {
const chunkSize = 50;
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
await processChunk(chunk);
await new Promise(resolve => setTimeout(resolve, 0));
}
}
3. Cumulative Layout Shift (CLS)
Target: < 0.1
What it measures: Visual stability (unexpected layout shifts)
Common causes:
- Images without dimensions
- Ads/embeds without reserved space
- Web fonts causing FOIT/FOUT
- Dynamic content insertion
How to fix:
<img
src="product.jpg"
width="800"
height="600"
alt="Product"
style={{ aspectRatio: '4/3' }}
/>
<div style={{ minHeight: '400px' }}>
{isLoading ? <Skeleton /> : <ProductGrid products={products} />}
</div>
@font-face {
font-family: 'Archivo';
src: url('/fonts/archivo.woff2') format('woff2');
font-display: swap;
font-weight: 400;
}
<link
rel="preload"
href="/fonts/archivo.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
Bundle Optimization
Analyze Bundle Size
npm install --save-dev webpack-bundle-analyzer
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
reportFilename: 'bundle-report.html',
}),
],
};
npm run build && open dist/bundle-report.html
Tree Shaking
import _ from 'lodash';
const result = _.debounce(fn, 300);
import { debounce } from 'lodash-es';
const result = debounce(fn, 300);
const debounce = (fn: Function, ms: number) => {
let timeout: NodeJS.Timeout;
return (...args: any[]) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), ms);
};
};
Code Splitting
import { lazy, Suspense } from 'react';
const ProductPage = lazy(() => import('./pages/ProductPage'));
const CheckoutPage = lazy(() => import('./pages/CheckoutPage'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/products/:id" element={<ProductPage />} />
<Route path="/checkout" element={<CheckoutPage />} />
</Routes>
</Suspense>
);
}
const HeavyChart = lazy(() => import('./HeavyChart'));
function Dashboard() {
return (
<div>
<Suspense fallback={<div>Loading chart...</div>}>
<HeavyChart data={data} />
</Suspense>
</div>
);
}
Image Optimization
Formats
| Format | Use Case | Compression |
|---|
| WebP | Modern browsers | 25-35% smaller than JPEG |
| AVIF | Cutting edge | 50% smaller than JPEG |
| JPEG | Fallback | Good for photos |
| PNG | Transparency | Lossless, larger |
| SVG | Icons, logos | Vector, scalable |
Responsive Images
<picture>
<source
srcset="
/images/ring-400.avif 400w,
/images/ring-800.avif 800w,
/images/ring-1200.avif 1200w
"
type="image/avif"
/>
<source
srcset="
/images/ring-400.webp 400w,
/images/ring-800.webp 800w,
/images/ring-1200.webp 1200w
"
type="image/webp"
/>
<img
src="/images/ring-800.jpg"
srcset="
/images/ring-400.jpg 400w,
/images/ring-800.jpg 800w,
/images/ring-1200.jpg 1200w
"
sizes="(max-width: 768px) 100vw, 800px"
alt="Luxury Ring"
width="800"
height="800"
loading="lazy"
/>
</picture>
Lazy Loading
<img src="image.jpg" loading="lazy" alt="Product" />
import { useEffect, useRef, useState } from 'react';
function LazyImage({ src, alt }: { src: string; alt: string }) {
const [isLoaded, setIsLoaded] = useState(false);
const imgRef = useRef<HTMLImageElement>(null);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsLoaded(true);
observer.disconnect();
}
});
},
{ rootMargin: '100px' }
);
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, []);
return (
<img
ref={imgRef}
src={isLoaded ? src : '/placeholder.jpg'}
alt={alt}
style={{ filter: isLoaded ? 'none' : 'blur(20px)' }}
/>
);
}
JavaScript Performance
Debouncing & Throttling
function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number
): (...args: Parameters<T>) => void {
let timeoutId: NodeJS.Timeout;
return (...args: Parameters<T>) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn(...args), delay);
};
}
const handleSearch = debounce((query: string) => {
fetchSearchResults(query);
}, 300);
function throttle<T extends (...args: any[]) => any>(
fn: T,
limit: number
): (...args: Parameters<T>) => void {
let inThrottle = false;
return (...args: Parameters<T>) => {
if (!inThrottle) {
fn(...args);
inThrottle = true;
setTimeout(() => (inThrottle = false), limit);
}
};
}
const handleScroll = throttle(() => {
console.log('Scrolled');
}, 100);
Memoization
import { useMemo, useCallback } from 'react';
function ProductList({ products, filters }) {
const filteredProducts = useMemo(() => {
return products.filter((p) => {
return filters.every((f) => f.fn(p));
});
}, [products, filters]);
const handleAddToCart = useCallback((productId: string) => {
addToCart(productId);
}, []);
return (
<div>
{filteredProducts.map((p) => (
<ProductCard
key={p.id}
product={p}
onAddToCart={handleAddToCart}
/>
))}
</div>
);
}
WordPress-Specific Optimizations
1. Caching
function get_skyyrose_featured_products() {
$cache_key = 'skyyrose_featured_products';
$products = get_transient($cache_key);
if (false === $products) {
$products = wc_get_products([
'meta_key' => '_featured',
'meta_value' => 'yes',
'limit' => 8,
]);
set_transient($cache_key, $products, HOUR_IN_SECONDS);
}
return $products;
}
2. Database Optimization
add_filter('woocommerce_product_query', function($q) {
$q->set('fields', 'ids');
return $q;
});
global $wpdb;
$wpdb->query("
CREATE INDEX idx_skyyrose_collection
ON {$wpdb->postmeta} (meta_key, meta_value)
WHERE meta_key = '_skyyrose_collection'
");
3. Asset Optimization
function skyyrose_defer_scripts($tag, $handle) {
$defer_scripts = ['elementor-frontend', 'wp-block-library'];
if (in_array($handle, $defer_scripts)) {
return str_replace(' src', ' defer src', $tag);
}
return $tag;
}
add_filter('script_loader_tag', 'skyyrose_defer_scripts', 10, 2);
function skyyrose_dequeue_unused_css() {
if (!is_cart() && !is_checkout()) {
wp_dequeue_style('wc-blocks-style');
}
}
add_action('wp_enqueue_scripts', 'skyyrose_dequeue_unused_css', 100);
3D Performance (Three.js / Babylon.js)
Level of Detail (LOD)
import { LOD, Mesh, SphereGeometry, MeshStandardMaterial } from 'three';
function createLODModel() {
const lod = new LOD();
const highDetail = new Mesh(
new SphereGeometry(1, 64, 64),
new MeshStandardMaterial()
);
lod.addLevel(highDetail, 0);
const mediumDetail = new Mesh(
new SphereGeometry(1, 32, 32),
new MeshStandardMaterial()
);
lod.addLevel(mediumDetail, 50);
const lowDetail = new Mesh(
new SphereGeometry(1, 16, 16),
new MeshStandardMaterial()
);
lod.addLevel(lowDetail, 100);
return lod;
}
Instancing for Multiple Objects
import { InstancedMesh, Object3D } from 'three';
const geometry = new BoxGeometry(1, 1, 1);
const material = new MeshStandardMaterial({ color: '#B76E79' });
const instancedMesh = new InstancedMesh(geometry, material, 1000);
const dummy = new Object3D();
for (let i = 0; i < 1000; i++) {
dummy.position.set(
Math.random() * 100 - 50,
Math.random() * 100 - 50,
Math.random() * 100 - 50
);
dummy.updateMatrix();
instancedMesh.setMatrixAt(i, dummy.matrix);
}
instancedMesh.instanceMatrix.needsUpdate = true;
scene.add(instancedMesh);
Monitoring & Measurement
Lighthouse CI
name: Lighthouse CI
on: [pull_request]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npm run build
- name: Run Lighthouse CI
run: |
npm install -g @lhci/cli
lhci autorun --config=.lighthouserc.json
{
"ci": {
"collect": {
"url": ["http://localhost:3000/"],
"numberOfRuns": 3
},
"assert": {
"assertions": {
"categories:performance": ["error", { "minScore": 0.9 }],
"categories:accessibility": ["error", { "minScore": 0.9 }],
"first-contentful-paint": ["error", { "maxNumericValue": 2000 }],
"largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }]
}
}
}
}
Real User Monitoring (RUM)
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
function sendToAnalytics(metric: any) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
delta: metric.delta,
id: metric.id,
});
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/analytics', body);
} else {
fetch('/api/analytics', { method: 'POST', body, keepalive: true });
}
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTFB(sendToAnalytics);
Performance Budget
Set Limits
| Metric | Budget | Current | Status |
|---|
| LCP | < 2.5s | 2.1s | ✅ |
| FID | < 100ms | 85ms | ✅ |
| CLS | < 0.1 | 0.05 | ✅ |
| JavaScript | < 200KB | 180KB | ✅ |
| CSS | < 50KB | 45KB | ✅ |
| Images | < 500KB | 450KB | ✅ |
| Total | < 1MB | 875KB | ✅ |
Enforce in CI
{
"scripts": {
"build": "next build",
"analyze": "ANALYZE=true next build",
"size-limit": "size-limit"
},
"size-limit": [
{
"path": "dist/**/*.js",
"limit": "200 KB"
},
{
"path": "dist/**/*.css",
"limit": "50 KB"
}
]
}
Quick Wins Checklist
For SkyyRose (or any site):
When User Reports Performance Issues
- Measure first: Run Lighthouse, check Network tab
- Identify bottleneck: LCP? FID? CLS? Large bundle?
- Prioritize: Focus on highest impact issues
- Implement fixes: One at a time, measure impact
- Monitor: Set up continuous monitoring
Always provide specific, measurable improvements with before/after metrics.