spb/ultra-sharp-agent-skills Public
Ultra-Sharp Agent Skills — a research-first skill-authoring system + 72 production-ready skills for AI agents.
Python 100%
1<!--2Author: Simon-Pierre Boucher3Contact: contact@spboucher.ai4-->56# Patterns — Optimizing Web Performance78## Contents9- LCP: hero image and critical path10- Images: responsive, modern, shift-free11- Fonts12- JavaScript: splitting and on-interaction loading13- Third-party scripts: facades and idle loading14- INP: breaking long tasks15- Measuring16- Gotchas1718## LCP: hero image and critical path1920```html21<head>22 <link rel="preconnect" href="https://cdn.example.com">23 <link rel="preload" as="image" href="hero-1200.avif"24 imagesrcset="hero-800.avif 800w, hero-1200.avif 1200w" imagesizes="100vw">25 <style>/* inlined critical CSS: layout + above-the-fold only */</style>26 <script src="/app.js" defer></script>27</head>28<body>29 <img src="hero-1200.avif"30 srcset="hero-800.avif 800w, hero-1200.avif 1200w" sizes="100vw"31 width="1200" height="600" fetchpriority="high" alt="…">32```3334## Images: responsive, modern, shift-free3536```html37<picture>38 <source type="image/avif" srcset="chart-400.avif 400w, chart-800.avif 800w">39 <source type="image/webp" srcset="chart-400.webp 400w, chart-800.webp 800w">40 <img src="chart-800.jpg" srcset="chart-400.jpg 400w, chart-800.jpg 800w"41 sizes="(max-width: 600px) 100vw, 50vw"42 width="800" height="500" loading="lazy" decoding="async" alt="…">43</picture>44```4546- `width`/`height` (or CSS `aspect-ratio`) on every `img`/`video`/`iframe` — CLS zero-cost insurance.47- `loading="lazy"` only below the fold; the browser handles the rest.4849## Fonts5051```html52<link rel="preload" as="font" type="font/woff2" href="/fonts/inter-var.woff2" crossorigin>53```5455```css56@font-face {57 font-family: 'Inter';58 src: url('/fonts/inter-var.woff2') format('woff2');59 font-weight: 100 900; /* one variable file replaces 5 static weights */60 font-display: swap; /* text visible immediately with fallback */61}62/* Reduce swap-induced shift: size-adjusted fallback */63@font-face {64 font-family: 'Inter-fallback';65 src: local('Arial');66 size-adjust: 107%; /* match Inter's metrics; tune per family */67}68```6970Subset with `pyftsubset` (fonttools) to the scripts actually used — typically 30–100 KB → under 15 KB.7172## JavaScript: splitting and on-interaction loading7374```tsx75// Route-level splitting (React Router / Next.js does this per page by default)76const Settings = lazy(() => import('./Settings'));7778// Widget on interaction — nothing loads until the user needs it79button.addEventListener('click', async () => {80 const { openChart } = await import('./chart.js');81 openChart(data);82}, { once: true });83```8485```bash86# Find what's actually in the bundle before removing anything87npx source-map-explorer dist/assets/*.js88```8990## Third-party scripts: facades and idle loading9192```html93<!-- Facade: static thumbnail replaces a 500 KB embed until clicked -->94<button class="yt-facade" data-id="VIDEO_ID"95 style="background: url('https://i.ytimg.com/vi/VIDEO_ID/hqdefault.jpg')">▶</button>96<script>97 document.querySelector('.yt-facade').addEventListener('click', (e) => {98 const iframe = document.createElement('iframe');99 iframe.src = `https://www.youtube.com/embed/${e.currentTarget.dataset.id}?autoplay=1`;100 iframe.width = 560; iframe.height = 315; iframe.allow = 'autoplay';101 e.currentTarget.replaceWith(iframe);102 }, { once: true });103</script>104105<!-- Analytics after everything else -->106<script>107 addEventListener('load', () => {108 requestIdleCallback(() => import('/analytics.js'));109 });110</script>111```112113## INP: breaking long tasks114115```js116// Yield to the main thread between chunks of work (>50 ms tasks block input)117async function processRows(rows) {118 for (const chunk of chunks(rows, 200)) { // 200 rows ≈ stays under 50 ms119 renderChunk(chunk);120 await (scheduler.yield?.() ?? new Promise(r => setTimeout(r)));121 }122}123```124125Pure computation (parsing, diffing, search indexing) → Web Worker; the main thread only renders.126127## Measuring128129```bash130npx lighthouse https://example.com --preset=perf --form-factor=mobile --view131```132133```js134// Field data from real users135import { onLCP, onINP, onCLS } from 'web-vitals';136[onLCP, onINP, onCLS].forEach(fn => fn(m => navigator.sendBeacon('/vitals', JSON.stringify(m))));137```138139## Gotchas140141- **Lazy-loading the LCP image** is the single most common self-inflicted LCP142 regression — audit every `loading="lazy"` above the fold.143- **`preload` overuse** starves the network of bandwidth for actual critical144 resources — preload at most the LCP image and one font.145- **`display: none` fonts still download** if declared in CSS — subset instead.146- **CLS from late-loading banners/toolbars**: reserve the slot with147 `min-height` even when content is conditional.148- **Debounce vs INP**: debouncing helps continuous input, but a *slow handler*149 needs chunking (see INP section) — debounce doesn't shorten the task.150- **Bundle analyzer lies about tree-shaking** until you build in production151 mode — always analyze the production build.152- **CDN cache misses** dominate TTFB for global users — check `cf-cache-status`153 / `x-cache` headers before optimizing the payload.154