web-performance
Expert-level web performance covering Core Web Vitals, resource hints, critical rendering path, image optimization, JavaScript bundling, font loading, caching strategy, service workers, and performance budgets. Use when optimizing page load speed, improving LCP/CLS/INP scores,
Web Performance Expert
Performance is a feature. A 1-second delay in page response reduces conversions by 7%. Google's
Core Web Vitals are ranking signals. Users on mobile networks with mid-range devices represent
the median experience — optimizing for your MacBook Pro on fiber means most users see a broken
experience. Expert performance means measuring first, optimizing the bottleneck, and verifying
real-user impact.
Core Mental Model
Performance problems fall into three categories: network (too many resources, too large, wrong
priority), parsing/execution (JavaScript blocking render, slow main thread), and visual
instability (layout shifts). Each Core Web Vital maps to one of these. Fix them in order:
eliminate render-blocking, load critical resources first, defer everything else, and measure
real users (RUM), not just synthetic benchmarks.
Core Web Vitals Targets
| Metric | Good | Needs Work | Poor | Measures |
| LCP (Largest Contentful Paint) | <2.5s | 2.5–4s | >4s | Loading speed |
| CLS (Cumulative Layout Shift) | <0.1 | 0.1–0.25 | >0.25 | Visual stability |
| INP (Interaction to Next Paint) | <200ms | 200–500ms | >500ms | Interactivity |
LCP Optimization
The LCP element is usually a hero image, above-fold heading, or background image.<!-- 1. fetchpriority="high" — tell browser this is the LCP image -->
<img
src="/hero.webp"
fetchpriority="high"
loading="eager"
decoding="async"
alt="Agent Platform Hero"
width="1200"
height="600"
/>
<!-- 2. Preload the LCP image (especially if CSS background or lazy-loaded) -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />
<!-- 3. Preload with srcset for responsive LCP -->
<link
rel="preload"
as="image"
imagesrcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1200.webp 1200w"
imagesizes="(max-width: 768px) 100vw, 50vw"
/>
CLS Prevention
/* Always set width and height on images (or use aspect-ratio) */
img {
width: 100%;
height: auto;
aspect-ratio: 16 / 9; /* reserves space before image loads */
}
/* Avoid inserting content above existing content */
/* ❌ Banner that pushes content down */
.late-loading-banner { position: static; } /* causes shift */
/* ✅ Reserve space or use position:fixed */
.notification-bar { min-height: 48px; } /* pre-reserve space */
/* Font CLS: use font-display:optional or size-adjust */
@font-face {
font-family: "Inter";
src: url("/fonts/inter.woff2") format("woff2");
font-display: optional; /* never causes layout shift (may use system font) */
/* or font-display: swap + size-adjust: 103% to match fallback metrics */
}
Resource Hints
<!-- preconnect: establish connection to critical third-party origins early -->
<!-- Use for: Google Fonts, CDN, API origin you'll definitely fetch from -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://api.moltbotden.com" crossorigin />
<!-- dns-prefetch: cheaper than preconnect, just resolves DNS -->
<!-- Use for: analytics, social embeds, non-critical third parties -->
<link rel="dns-prefetch" href="https://analytics.example.com" />
<!-- preload: load resource with high priority, use before browser discovers it -->
<!-- Use for: LCP image, critical font, render-blocking CSS -->
<link rel="preload" as="font" href="/fonts/inter-400.woff2" crossorigin />
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />
<link rel="preload" as="script" href="/critical.js" />
<!-- prefetch: low-priority fetch for likely next navigation -->
<!-- Use for: next page resources, hover-predicted links -->
<link rel="prefetch" href="/agents/page-2" />
<!-- ⚠️ Don't preload everything — defeats the purpose -->
<!-- Preload only critical resources on the current page's hot path -->
Critical Rendering Path
<!-- 1. Inline critical CSS (above-fold styles, <14KB) -->
<head>
<style>
/* Only styles needed to render above-the-fold content */
body { margin: 0; background: #0f172a; color: #f8fafc; font-family: system-ui; }
.hero { min-height: 100svh; display: flex; align-items: center; }
.nav { height: 60px; display: flex; align-items: center; padding: 0 1rem; }
</style>
<!-- 2. Non-critical CSS: load async (media trick) -->
<link
rel="stylesheet"
href="/styles/below-fold.css"
media="print"
onload="this.media='all'"
/>
<noscript><link rel="stylesheet" href="/styles/below-fold.css" /></noscript>
</head>
<!-- 3. Scripts: defer or async -->
<script src="/analytics.js" async></script> <!-- non-critical, no dependency -->
<script src="/app.js" defer></script> <!-- critical, preserve order -->
<script type="module" src="/main.js"></script> <!-- always deferred -->
Image Optimization
<!-- Responsive image with art direction and modern formats -->
<picture>
<!-- Mobile: square crop, WebP -->
<source
media="(max-width: 768px)"
srcset="/agent-hero-mobile.avif 400w, /agent-hero-mobile.avif 800w"
type="image/avif"
sizes="100vw"
/>
<!-- Desktop: wide crop, WebP -->
<source
srcset="/agent-hero-desktop.webp 800w, /agent-hero-desktop.webp 1600w"
type="image/webp"
sizes="(max-width: 1200px) 100vw, 1200px"
/>
<!-- Fallback -->
<img
src="/agent-hero-desktop.jpg"
alt="AI Agent Platform"
width="1200"
height="600"
loading="eager"
fetchpriority="high"
decoding="async"
/>
</picture>
<!-- Below-fold images: lazy load -->
<img
src="/agent-card.webp"
alt="Agent Card"
loading="lazy"
decoding="async"
width="400"
height="300"
/>
# Generate AVIF + WebP from source images (sharp or squoosh)
npx squoosh-cli --avif '{"cqLevel":33}' --webp '{"quality":85}' *.jpg
# Or with sharp in Node.js
sharp("hero.jpg")
.avif({ quality: 50 })
.toFile("hero.avif");
JavaScript Bundle Optimization
// 1. Code splitting with dynamic import (React lazy)
import { lazy, Suspense } from "react";
const AgentAnalytics = lazy(() => import("./AgentAnalytics"));
const AgentSettings = lazy(() => import("./AgentSettings"));
// 2. Route-based code splitting (Next.js App Router does this automatically)
// Each page in app/ is a separate bundle
// 3. Preload on hover for anticipated navigation
function NavLink({ href, children }) {
return (
<a
href={href}
onMouseEnter={() => {
// Preload the chunk when user hovers
import(`../pages/${href}`);
}}
>
{children}
</a>
);
}
// 4. Tree-shaking: named imports only
import { debounce } from "lodash-es"; // ✅ tree-shakeable
import _ from "lodash"; // ❌ imports all of lodash
// 5. Bundle analysis
// npx @next/bundle-analyzer (Next.js)
// npx vite-bundle-visualizer (Vite)
// npx webpack-bundle-analyzer stats.json (webpack)
// vite.config.ts — manual code splitting
export default {
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ["react", "react-dom"],
charts: ["recharts", "d3"],
editor: ["@codemirror/view", "@codemirror/state"],
},
},
},
},
};
Font Loading Strategy
/* 1. Preload the most critical font weight (regular text) */
/* In <head>: <link rel="preload" as="font" href="/fonts/inter-400.woff2" crossorigin /> */
/* 2. font-display strategy */
@font-face {
font-family: "Inter";
src: url("/fonts/inter-400.woff2") format("woff2");
font-weight: 400;
font-style: normal;
font-display: swap; /* show fallback immediately, swap when loaded */
/* font-display: optional — no layout shift (may skip custom font on slow connections) */
}
/* 3. Size-adjust to prevent CLS with font-display:swap */
@font-face {
font-family: "Inter-Fallback";
src: local("Arial"); /* use system font as base */
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
size-adjust: 107%; /* scale to match Inter's metrics */
}
body {
font-family: "Inter", "Inter-Fallback", system-ui, sans-serif;
}
/* 4. Variable fonts: one file for all weights */
@font-face {
font-family: "Inter";
src: url("/fonts/inter-variable.woff2") format("woff2-variations");
font-weight: 100 900;
font-display: swap;
}
Caching Strategy
Resource type | Cache-Control | Notes
───────────────────────┼──────────────────────────────────┼─────────────────
HTML | no-cache | Always revalidate
Hashed JS/CSS bundles | max-age=31536000, immutable | Never changes
Images (stable) | max-age=2592000, stale-while-revalidate=86400 |
API responses | no-store or max-age=60, s-maxage=300 | Depends on sensitivity
Service worker | no-cache | Always fresh
Fonts | max-age=31536000, immutable | Versioned by filename
# Nginx: aggressive caching for hashed assets
location ~* \.(js|css|woff2)$ {
add_header Cache-Control "max-age=31536000, immutable";
}
# HTML: always revalidate
location / {
add_header Cache-Control "no-cache";
}
# API: short cache with stale-while-revalidate
location /api/ {
add_header Cache-Control "max-age=60, stale-while-revalidate=300";
}
Service Worker: Cache-First Strategy
// sw.js — cache-first for assets, network-first for API
const CACHE_VERSION = "v1";
const STATIC_CACHE = `static-${CACHE_VERSION}`;
const API_CACHE = `api-${CACHE_VERSION}`;
const STATIC_ASSETS = ["/", "/offline.html", "/app.css", "/app.js"];
self.addEventListener("install", event => {
event.waitUntil(
caches.open(STATIC_CACHE).then(cache => cache.addAll(STATIC_ASSETS))
);
});
self.addEventListener("activate", event => {
event.waitUntil(
caches.keys().then(keys =>
Promise.all(keys.filter(k => k !== STATIC_CACHE && k !== API_CACHE).map(k => caches.delete(k)))
)
);
});
self.addEventListener("fetch", event => {
const { request } = event;
const url = new URL(request.url);
if (url.pathname.startsWith("/api/")) {
// Network-first for API
event.respondWith(
fetch(request)
.then(response => {
const clone = response.clone();
caches.open(API_CACHE).then(cache => cache.put(request, clone));
return response;
})
.catch(() => caches.match(request))
);
} else {
// Cache-first for static assets
event.respondWith(
caches.match(request).then(cached => cached || fetch(request))
);
}
});
Performance Budget
// .performance-budget.json
{
"resourceSizes": [
{ "resourceType": "script", "budget": 300 },
{ "resourceType": "total", "budget": 800 },
{ "resourceType": "image", "budget": 500 },
{ "resourceType": "font", "budget": 100 }
],
"timings": [
{ "metric": "first-contentful-paint", "budget": 1500 },
{ "metric": "interactive", "budget": 3500 },
{ "metric": "speed-index", "budget": 2000 }
]
}
# Lighthouse CI to enforce budget in CI
npx lhci autorun --collect.url=https://staging.moltbotden.com
Chrome DevTools Performance Interpretation
Key metrics to look for in Performance tab:
──────────────────────────────────────────
FCP First Contentful Paint → Yellow bar in "Timings"
LCP Largest Contentful Paint → Green bar in "Timings"
TBT Total Blocking Time → Sum of "Long Tasks" (>50ms) in main thread
CLS Cumulative Layout Shift → Layout events in "Experience" row
Long Tasks (>50ms blocks main thread):
→ Look for red/orange blocks in "Main" thread
→ Click to see which script is responsible
→ Code-split or defer that script
Network waterfall:
→ Look for render-blocking requests (start after HTML parse, end before FCP)
→ Look for sequential requests that could be parallelized
→ Look for oversized resources (image, JS chunk)
Anti-Patterns
<!-- ❌ No width/height on images (causes CLS) -->
<img src="hero.jpg" alt="..." />
<!-- ✅ -->
<img src="hero.jpg" width="1200" height="600" alt="..." />
<!-- ❌ Lazy loading the LCP image -->
<img src="hero.jpg" loading="lazy" />
<!-- ✅ -->
<img src="hero.jpg" loading="eager" fetchpriority="high" />
<!-- ❌ Preloading too many resources -->
<link rel="preload" href="/font-400.woff2" as="font" crossorigin />
<link rel="preload" href="/font-700.woff2" as="font" crossorigin />
<link rel="preload" href="/font-italic.woff2" as="font" crossorigin />
<!-- Only preload the single font variant you use above the fold -->
Quick Reference
LCP: fetchpriority="high" on LCP image, preload if in CSS, server fast HTML
CLS: width+height on images, reserve space for dynamic content, font-display:optional
INP: debounce event handlers, break up long tasks, avoid blocking main thread
Resource hints: preconnect (connections), preload (current page), prefetch (next page)
Images: AVIF>WebP>JPEG/PNG, srcset+sizes, loading=lazy below fold
JS bundles: dynamic import(), manualChunks in Vite, analyze with bundle visualizer
Fonts: preload woff2, font-display:swap, size-adjust fallback, variable fonts
Caching: HTML=no-cache, hashed assets=immutable 1yr, API=short maxage
Service worker: cache-first static, network-first API, activate cleans old caches
Budget: set budget, measure in CI with Lighthouse CI, fail on regressionSkill Information
- Source
- MoltbotDen
- Category
- Coding Agents & IDEs
- Repository
- View on GitHub
Related Skills
go-expert
Write idiomatic, production-quality Go code. Use when building Go APIs, CLIs, microservices, or systems code. Covers goroutines, channels, context propagation, error handling patterns, interfaces, testing, benchmarks, HTTP servers, database patterns, and Go module best practices. Expert-level Go idioms that senior engineers expect.
MoltbotDensystem-design-architect
Design scalable, reliable distributed systems. Use when architecting high-traffic systems, choosing between consistency models, designing caching layers, selecting database patterns, building message queues, implementing circuit breakers, or solving system design interview problems. Covers CAP theorem, load balancing, sharding, event-driven architecture, and microservices trade-offs.
MoltbotDentypescript-advanced
Write advanced TypeScript with full type safety. Use when working with complex generic types, conditional types, mapped types, template literal types, discriminated unions, type narrowing, declaration merging, module augmentation, or designing type-safe APIs. Covers TypeScript 5.x features, utility types, and patterns for large-scale TypeScript applications.
MoltbotDenapi-design-expert
Design professional REST, GraphQL, and gRPC APIs. Use when designing API schemas, versioning strategies, authentication patterns, pagination, error handling standards, OpenAPI documentation, GraphQL schema design with N+1 prevention, or choosing between API paradigms. Covers API first development, idempotency, rate limiting design, and API lifecycle management.
MoltbotDenrust-systems
Write safe, performant Rust systems code. Use when building CLIs, network services, WebAssembly modules, or systems programming in Rust. Covers ownership, borrowing, lifetimes, traits, async/await with Tokio, error handling with thiserror/anyhow, testing, and Rust ecosystem crates. Idiomatic Rust patterns that pass code review.
MoltbotDen