frontend-performance/SKILL.md
Frontend performance optimisation covering Core Web Vitals (LCP, INP, CLS), image optimisation, JavaScript bundling, CSS efficiency, font loading, rendering performance, network optimisation, and real-device measurement. Use when building or...
npx skillsauth add peterbamuhigire/skills-web-dev frontend-performanceInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
frontend-performance or would be better handled by a more specific companion skill.SKILL.md first, then load only the referenced deep-dive files that are necessary for the task.| Companion Skill | When to Load |
|---|---|
| responsive-design | Image and layout optimisation |
| motion-design | Animation performance |
| webapp-gui-design | Web app implementation |
| image-compression | Image upload compression patterns |
| Metric | Good | Needs Work | Poor | What It Measures | |---|---|---|---|---| | LCP | < 2.5s | 2.5-4s | > 4s | Largest visible content painted | | INP | < 200ms | 200-500ms | > 500ms | Input responsiveness (replaces FID) | | CLS | < 0.1 | 0.1-0.25 | > 0.25 | Unexpected layout shifts |
| Technique | Impact | How |
|---|---|---|
| Modern formats | 25-50% smaller | WebP (95% support), AVIF (85% support) |
| Responsive images | Serve right size | srcset with width descriptors |
| Lazy loading | Defer offscreen | loading="lazy" on below-fold images |
| Explicit dimensions | Prevent CLS | Always set width and height attributes |
| Eager LCP image | Faster paint | loading="eager" + fetchpriority="high" on hero |
| Max 2x density | Diminishing returns | Don't serve 3x or 4x images |
<!-- Hero image (LCP candidate) -->
<img
src="hero-800.webp"
srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
sizes="100vw"
width="1200" height="600"
alt="Description"
loading="eager"
fetchpriority="high"
decoding="async"
>
| Technique | Impact |
|---|---|
| Code splitting | Load only what's needed for current route |
| Tree shaking | Remove unused exports |
| Dynamic imports | Lazy-load heavy components (import()) |
| Defer non-critical | <script defer> for below-fold interactivity |
| Minification | Remove whitespace, shorten variables |
| Bundle analysis | Find and eliminate large dependencies |
Budget: Total JS < 200KB compressed for initial load (main bundle).
| Technique | Impact |
|---|---|
| Critical CSS inline | Render above-fold without blocking |
| Remove unused CSS | PurgeCSS or framework tree-shaking |
| Avoid @import | Causes sequential loading (use <link> instead) |
| Minify | Remove whitespace and comments |
| Logical properties | Reduce RTL-specific overrides |
@font-face {
font-family: 'Brand';
src: url('brand.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap; /* Show fallback immediately, swap when loaded */
}
| Strategy | When |
|---|---|
| font-display: swap | Default — prevents invisible text |
| font-display: optional | Performance-critical — skip custom font if slow |
| preload | For critical above-fold fonts only |
| Subset | Include only needed character ranges |
| Variable fonts | One file for all weights (if using 3+ weights) |
<!-- Preload critical font -->
<link rel="preload" href="brand.woff2" as="font" type="font/woff2" crossorigin>
<!-- DNS prefetch for third-party domains -->
<link rel="dns-prefetch" href="https://api.example.com">
<!-- Preconnect for critical third-party -->
<link rel="preconnect" href="https://cdn.example.com" crossorigin>
<!-- Prefetch next likely page -->
<link rel="prefetch" href="/dashboard">
Layout thrashing occurs when you read layout properties then write them in a loop, forcing the browser to recalculate layout on every iteration.
// BAD: forces layout recalculation per iteration
items.forEach(item => {
const height = item.offsetHeight; // READ (forces layout)
item.style.height = height + 10 + 'px'; // WRITE (invalidates layout)
});
// GOOD: batch reads, then batch writes
const heights = items.map(item => item.offsetHeight); // all READS
items.forEach((item, i) => {
item.style.height = heights[i] + 10 + 'px'; // all WRITES
});
Avoid animating or frequently changing:
width, height, top, left, right, bottommargin, padding, border-widthfont-size, line-heightdisplay, position, floatSafe to animate: transform, opacity (compositor-only, GPU-accelerated)
will-change: transform on elements about to animate (remove after)contain: layout or contain: paint to isolate repaint areascontent-visibility: auto for long scrollable listsFor lists with 100+ items, virtualise — only render visible items plus a buffer.
| Platform | Library |
|---|---|
| React | react-window, @tanstack/virtual |
| Vue | vue-virtual-scroller |
| Vanilla | IntersectionObserver + manual pool |
| Cause | Fix |
|---|---|
| Images without dimensions | Always set width and height attributes |
| Ads/embeds loading late | Reserve space with aspect-ratio or fixed container |
| Web fonts causing reflow | Use font-display: swap + size-adjust fallback |
| Dynamic content above viewport | Insert below current scroll position |
| Late-loading CSS | Inline critical CSS |
| Lazy components popping in | Use skeleton placeholders with exact dimensions |
/* Reserve space for dynamic content */
.image-container {
aspect-ratio: 16 / 9;
width: 100%;
background: var(--color-surface-alt); /* placeholder color */
}
requestIdleCallback for non-urgent work// Break up long tasks
async function processItems(items) {
for (const item of items) {
processItem(item);
// Yield every 50ms to keep UI responsive
if (performance.now() - start > 50) {
await new Promise(resolve => setTimeout(resolve, 0));
start = performance.now();
}
}
}
button.addEventListener('click', async () => {
// 1. Immediate visual feedback
button.classList.add('loading');
// 2. Yield to let browser paint
await new Promise(resolve => requestAnimationFrame(resolve));
// 3. Do the actual work
await saveData();
// 4. Update UI with result
button.classList.remove('loading');
});
quic module.Cache-Control: public, max-age=31536000, immutable for hashed assets| Format | Support | Use | |---|---|---| | Brotli | 97% | Default (20-30% smaller than gzip) | | gzip | 99% | Fallback |
// Cache-first for static assets, network-first for API
self.addEventListener('fetch', event => {
if (event.request.url.includes('/api/')) {
event.respondWith(networkFirst(event.request));
} else {
event.respondWith(cacheFirst(event.request));
}
});
React.memo() for expensive pure componentsuseMemo / useCallback for referential stabilityReact.lazy() + Suspense for code splitting<template> for frequently cloned structures| Resource | Budget (Compressed) | |---|---| | HTML | < 50 KB | | CSS (total) | < 100 KB | | JS (initial) | < 200 KB | | Fonts | < 100 KB (1-2 fonts max) | | Images (above fold) | < 200 KB total | | Total initial load | < 650 KB |
bundlesize or size-limit npm packagesBefore shipping, measure on a real mid-range device over a 3G/4G connection:
font-display: swap with preload for critical fontsSources: Impeccable optimize skill (Bakaus, 2025); Web.dev — Core Web Vitals; Google Lighthouse documentation; MDN Performance guides.
data-ai
Use when adding AI-powered analytics to a SaaS platform — semantic search over business data, natural language queries, trend detection, anomaly alerts, and AI-generated insights for dashboards. Covers embeddings, NL2SQL, and per-tenant analytics...
data-ai
Design AI-powered analytics dashboards — what metrics to show, how to display AI predictions and confidence, drill-down patterns, KPI cards, trend visualisation, AI Insights panels, export design, and role-based dashboard variants. Invoke when...
development
Use when designing, building, reviewing, or upgrading production software systems that must be secure, performant, maintainable, scalable, and user-centered. Apply before writing specs, code, architecture, APIs, databases, mobile apps, SaaS platforms, or ERP systems.
development
Professional web app UI using commercial templates (Tabler/Bootstrap 5) with strong frontend design direction when needed. Use for CRUD interfaces, dashboards, admin panels with SweetAlert2, DataTables, Flatpickr. Clone seeder-page.php, use...