# Performance

> Web performance rules - Core Web Vitals, Lighthouse, Chrome DevTools, critical rendering path, resource hints, critical CSS, caching and compression, service workers, streaming, bundle optimization, code splitting, tree shaking, lazy loading, images, fonts, animation and interaction performance, performance budgets

- Skill: `14bryanespinoza/performance` (Agent Skill)
- Install (CLI): `npx skillmds@latest add 14bryanespinoza/performance`
- Raw SKILL.md: https://api.skillmd.com/api/skills/14bryanespinoza/performance/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: 14BryanEspinoza (https://skillmd.com/u/14bryanespinoza)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/14bryanespinoza/performance

---


# Performance — Rules and Conventions

---

## 1. Philosophy

1. **User-centric metrics** — Optimize for Core Web Vitals (LCP, INP, CLS), not synthetic scores.
2. **Lab + Field** — Lab for regression detection, field (RUM) for real-user impact.
3. **Budget-driven** — Set performance budgets. CI fails if exceeded.
4. **Progressive enhancement** — Core content works without JS. Enhance progressively.
5. **Measure before optimize** — Profile first. Guesswork wastes time.

---

## 2. Minimum Versions

| Technology | Minimum Version |
| ---------- | --------------- |
| Node.js    | 22+             |
| pnpm       | 11+             |
| Chrome     | 120+ (DevTools) |
| Lighthouse | 11+             |

---

## 3. Measurement: Lab vs Field

| Aspect          | Lab (Synthetic)                          | Field (RUM)                                 |
| --------------- | ---------------------------------------- | ------------------------------------------- |
| **Tools**       | Lighthouse, WebPageTest, Chrome DevTools | Chrome UX Report (CrUX), web-vitals library |
| **Environment** | Controlled (throttled CPU/network)       | Real users, real devices, real networks     |
| **Use case**    | CI regression, PR comparison             | Business impact, prioritization             |
| **Frequency**   | Every PR                                 | Continuous                                  |

### web-vitals (field)

```bash
pnpm add web-vitals
```

```js
// analytics.js
import { onCLS, onINP, onLCP, onFCP, onTTFB } from "web-vitals";

function sendToAnalytics(metric) {
  fetch("/analytics", {
    method: "POST",
    body: JSON.stringify(metric),
    keepalive: true,
  });
}

onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);
```

---

## 4. Core Web Vitals

| Metric                              | Good    | Needs Improvement | Poor    | Measures         |
| ----------------------------------- | ------- | ----------------- | ------- | ---------------- |
| **LCP** (Largest Contentful Paint)  | ≤ 2.5s  | 2.5–4s            | > 4s    | Loading          |
| **INP** (Interaction to Next Paint) | ≤ 200ms | 200–500ms         | > 500ms | Interactivity    |
| **CLS** (Cumulative Layout Shift)   | ≤ 0.1   | 0.1–0.25          | > 0.25  | Visual stability |

### LCP optimization

```html
<!-- Preload LCP image -->
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high" />

<!-- Or prioritize in HTML -->
<img
  src="/hero.avif"
  alt="..."
  fetchpriority="high"
  width="1200"
  height="600"
/>
```

### INP optimization

- **Reduce main thread work** — code splitting, web workers
- **Avoid long tasks** — yield with `scheduler.yield()` or `setTimeout`
- **Optimize event handlers** — debounce, passive listeners

### CLS prevention

```css
/* Reserve space for images/ads/embeds */
img,
video,
iframe {
  aspect-ratio: 16 / 9;
}
.ad-slot {
  min-height: 250px;
}

/* Font loading */
@font-face {
  font-display: swap;
}
```

---

## 5. Critical Rendering Path

### Blocking resources

```html
<head>
  <!-- Critical CSS inline -->
  <style>
    /* critical.css */
  </style>

  <!-- Preload non-critical CSS -->
  <link
    rel="preload"
    href="/styles.css"
    as="style"
    onload="this.rel='stylesheet'"
  />

  <!-- Defer JS -->
  <script src="/app.js" defer></script>

  <!-- DNS prefetch for third-parties -->
  <link rel="dns-prefetch" href="//fonts.googleapis.com" />
  <link rel="preconnect" href="//cdn.example.com" crossorigin />
</head>
```

### Rules

- **Inline critical CSS** — above-the-fold styles
- **Preload key resources** — LCP image, fonts, critical CSS
- **Defer non-critical JS** — `defer` or module scripts
- **Preconnect third-party origins** — fonts, APIs, CDNs

---

## 6. Resource Hints

| Hint            | Use Case                        | Example                                                          |
| --------------- | ------------------------------- | ---------------------------------------------------------------- |
| `preload`       | Critical resource (same origin) | `<link rel="preload" as="font" href="/font.woff2" crossorigin>`  |
| `prefetch`      | Next navigation likely          | `<link rel="prefetch" href="/about">`                            |
| `preconnect`    | Third-party origin              | `<link rel="preconnect" href="//fonts.gstatic.com" crossorigin>` |
| `dns-prefetch`  | DNS only (fallback)             | `<link rel="dns-prefetch" href="//api.example.com">`             |
| `modulepreload` | JS modules                      | `<link rel="modulepreload" href="/app.js">`                      |

### Rules Resource

- **Preload** — LCP image, critical font, critical CSS
- **Preconnect** — all third-party origins used
- **Prefetch** — hover/intent-based for next page
- **Don't over-hint** — wastes bandwidth

---

## 7. Critical CSS

### Extraction (build-time)

```bash
pnpm add -D critters
```

```ts
// vite.config.ts
import critters from "critters";

export default defineConfig({
  plugins: [
    critters({
      preload: "swap",
      pruneSource: true,
      inlineThreshold: 10000,
    }),
  ],
});
```

### Manual approach

```html
<head>
  <style>
    /* Critical: above-the-fold only */
    header { ... }
    .hero { ... }
    .btn { ... }
  </style>
  <link
    rel="preload"
    href="/styles.css"
    as="style"
    onload="this.rel='stylesheet'"
  />
  <noscript><link rel="stylesheet" href="/styles.css" /></noscript>
</head>
```

---

## 8. HTML & DOM (Compact)

### Document structure

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <!-- Critical meta, preloads, inline critical CSS -->
  </head>
  <body>
    <!-- Content first -->
    <main>...</main>
    <!-- Non-critical JS at end or deferred -->
  </body>
</html>
```

### Rules HTML & DOM

- **Minimize DOM depth** — flatter = faster style calc
- **Reduce DOM size** — < 1500 nodes, < 60 depth
- **Avoid layout thrashing** — batch reads/writes (see `javascript` skill)

---

## 9. CSS Performance (Compact)

### Efficient selectors

```css
/* ✅ Good */
.button {
}
.card__title {
}

/* ❌ Bad */
div > ul > li > a {
}
:global(.class) {
}
```

### Containment

```css
.widget {
  contain: layout style paint; /* or strict */
  content-visibility: auto;
  contain-intrinsic-size: 1000px;
}
```

### Animations

```css
/* ✅ GPU-accelerated */
transform: translateX(100px);
opacity: 0.5;

/* ❌ Triggers layout */
left: 100px;
width: 200px;
```

> **Animation details**: see `css` skill.

---

## 10. JavaScript Performance (Compact)

### Code splitting

```js
// Route-level
const Admin = lazy(() => import("./Admin"));

// Component-level
const HeavyChart = lazy(() => import("./HeavyChart"));
```

### Web Workers (off-main-thread)

```js
// worker.ts
self.onmessage = (e) => {
  const result = heavyComputation(e.data);
  self.postMessage(result);
};

// main.ts
const worker = new Worker(new URL("./worker.ts", import.meta.url));
worker.postMessage(data);
worker.onmessage = (e) => {
  /* result */
};
```

### Passive listeners

```js
element.addEventListener("touchstart", handler, { passive: true });
element.addEventListener("scroll", handler, { passive: true });
```

> **JS patterns**: see `javascript` skill.

---

## 11. Bundle Optimization (Reference)

> **Build config owned by `vite`, `esbuild`, `package-manager`** — this skill defines what to measure.

### What to measure

| Metric             | Tool                           | Target           |
| ------------------ | ------------------------------ | ---------------- |
| Total JS size      | `vite build --mode production` | < 170 KB gzipped |
| CSS size           | same                           | < 50 KB gzipped  |
| Number of requests | DevTools Network               | < 50             |
| Third-party size   | Lighthouse                     | < 20% total      |

### Vite config (reference)

```ts
// vite.config.ts
build: {
  target: 'es2022',
  minify: 'esbuild',
  cssCodeSplit: true,
  rollupOptions: {
    output: {
      manualChunks: {
        vendor: ['react', 'react-dom'],
        router: ['react-router-dom']
      }
    }
  }
}
```

> **Full config**: see `vite` and `esbuild` skills.

---

## 12. Code Splitting (Reference)

> **Implementation in `vite`/`esbuild`** — this skill defines strategy.

### Strategies

| Strategy             | When                                |
| -------------------- | ----------------------------------- |
| **Route-based**      | Pages, admin sections               |
| **Component-based**  | Heavy widgets (charts, editors)     |
| **Vendor splitting** | Stable dependencies (React, UI lib) |
| **Dynamic import**   | On-demand features                  |

---

## 13. Lazy Loading

### Images

```html
<!-- Native (all modern browsers) -->
<img src="/image.avif" loading="lazy" alt="..." width="800" height="600" />

<!-- LCP image — NEVER lazy -->
<img src="/hero.avif" loading="eager" fetchpriority="high" alt="..." />
```

### Iframes

```html
<iframe src="/embed" loading="lazy" title="..."></iframe>
```

### JS modules

```js
// IntersectionObserver trigger
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      import("./HeavyComponent").then((m) => m.default.mount(entry.target));
      observer.unobserve(entry.target);
    }
  });
});
observer.observe(document.querySelector("#heavy-component"));
```

---

## 14. Images (Essential)

### Formats

| Format      | Support  | Use Case                    |
| ----------- | -------- | --------------------------- |
| **AVIF**    | 95%+     | Best compression, photos    |
| **WebP**    | 97%+     | Fallback for AVIF           |
| **JPEG XL** | Emerging | Future-proof                |
| **SVG**     | 100%     | Icons, logos, illustrations |

### Responsive images

```html
<picture>
  <source type="image/avif" srcset="/img.avif" />
  <source type="image/webp" srcset="/img.webp" />
  <img src="/img.jpg" alt="..." width="800" height="600" loading="lazy" />
</picture>
```

### Rules Image

- **Width/height always** — prevents CLS
- **`loading="lazy"`** — all below-fold images
- **`fetchpriority="high"`** — LCP image only
- **Serve via CDN** — with `Cache-Control: immutable`

---

## 15. Fonts (Essential)

```css
@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-var.woff2") format("woff2");
  font-display: swap; /* Text visible during load */
  font-weight: 100 900; /* Variable font */
  size-adjust: 100.06%; /* Fallback metric override */
  ascent-override: 90%;
  descent-override: 22%;
  line-gap-override: 0%;
}
```

### Preload

```html
<link rel="preload" as="font" href="/fonts/inter-var.woff2" crossorigin />
```

### Rules Fonts

- **WOFF2 only** — best compression
- **Variable fonts** — one file for all weights
- **`font-display: swap`** — no invisible text
- **Subset** — only needed glyphs (latin, cyrillic, etc.)

---

## 16. Caching (Essential)

### Cache-Control headers

```nginx
# Immutable assets (hashed filenames)
location ~* \.(js|css|png|jpg|avif|woff2)$ {
  add_header Cache-Control "public, max-age=31536000, immutable";
}

# HTML (no cache or short)
location / {
  add_header Cache-Control "no-cache, must-revalidate";
}

# Fonts
location ~* \.woff2$ {
  add_header Cache-Control "public, max-age=31536000, immutable";
  add_header Access-Control-Allow-Origin "*";
}
```

### Rules Caching

- **Hashed filenames** — `app.[hash].js` → immutable cache
- **HTML no-cache** — always revalidate
- **Immutable for assets** — `max-age=31536000, immutable`

---

## 17. Compression (Essential)

```nginx
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

# Brotli (better compression)
brotli on;
brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
```

### Rules Compression

- **Enable both gzip + brotli** — brotli ~15% better
- **Min 1KB** — don't compress tiny responses
- **CDN handles** — usually automatic

---

## 18. Service Workers (Compact)

### Workbox (recommended)

```bash
pnpm add -D workbox-cli
```

```js
// workbox-config.js
module.exports = {
  globDirectory: "dist/",
  globPatterns: ["**/*.{js,css,html,ico,woff2,avif}"],
  swDest: "dist/sw.js",
  clientsClaim: true,
  skipWaiting: true,
  runtimeCaching: [
    {
      urlPattern: /^https:\/\/api\./,
      handler: "NetworkFirst",
      options: {
        networkTimeoutSeconds: 10,
        cacheableResponse: { statuses: [0, 200] },
      },
    },
    {
      urlPattern: /^https:\/\/cdn\./,
      handler: "CacheFirst",
      options: { expiration: { maxAgeSeconds: 31536000 } },
    },
  ],
};
```

### Registration

```js
// main.js
if ("serviceWorker" in navigator) {
  navigator.serviceWorker.register("/sw.js");
}
```

### Rules Services Workers

- **Precache static assets** — HTML, JS, CSS, fonts, images
- **Runtime cache API** — `NetworkFirst` for HTML/API, `CacheFirst` for static
- **`skipWaiting` + `clientsClaim`** — immediate activation

---

## 19. Performance Budgets (CI)

### Lighthouse CI

```yaml
# .github/workflows/lighthouse.yml
- uses: treosh/lighthouse-ci-action@v11
  with:
    urls: |
      https://staging.example.com
      https://staging.example.com/about
    budgetPath: ./lighthouse-budget.json
```

```json
// lighthouse-budget.json
{
  "ci": {
    "assert": {
      "assertions": {
        "categories:performance": ["error", { "minScore": 0.9 }],
        "categories:accessibility": ["error", { "minScore": 0.95 }],
        "categories:best-practices": ["error", { "minScore": 0.9 }],
        "categories:seo": ["error", { "minScore": 0.9 }]
      }
    }
  }
}
```

### Bundle budget (vite)

```ts
// vite.config.ts
build: {
  chunkSizeWarningLimit: 500, // KB
  reportCompressedSize: true
}
```

### Rules Performance Budgets

- **CI fails on budget exceed** — no merge if over
- **Track trends** — not just absolute values
- **Per-page budgets** — homepage stricter than admin

---

## 20. Methodology

Before using ANY performance pattern not documented in
this skill:

1. **MCP Context7** (priority): `context7_resolve-library-id` +
   `context7_query-docs` for Lighthouse, web-vitals, Workbox.
2. **Official docs**: web.dev, developer.chrome.com
   — verify current APIs.
3. **Project config**: `vite.config.ts`, `package.json`,
   CI configs — verify against actual setup.
4. **HARD RULE**: If not in this skill AND cannot be verified against
   2 authoritative sources → DO NOT USE IT. Document as assumption or risk in
   report to orchestrator.

---

## 21. Prohibitions

- ❌ Do not optimize without measuring — profile first
- ❌ Do not inline large CSS/JS — bloats HTML, blocks parsing
- ❌ Do not lazy-load LCP image — kills LCP
- ❌ Do not use `font-display: block` — invisible text
- ❌ Do not skip compression — free performance
- ❌ Do not cache HTML long-term — breaks updates
- ❌ Do not add third-party scripts without audit — audit first
- ❌ Do not animate layout properties — use transform/opacity

---

## 22. References

> **Note:** For HTML conventions (critical rendering path),
> see [HTML](../html/SKILL.md)
> **Note:** For CSS conventions (animations, containment),
> see [CSS](../css/SKILL.md)
> **Note:** For JavaScript conventions (code splitting, workers),
> see [JavaScript](../javascript/SKILL.md)
> **Note:** For package manager conventions (build scripts),
> see [Package Manager](../package-manager/SKILL.md)
> **Note:** For deploy CI/CD, see [Deploy](../deploy/SKILL.md)
> **Note:** For Vite build config, see [Vite](../vite/SKILL.md)
> **Note:** For esbuild config, see [esbuild](../esbuild/SKILL.md)

---

Last updated: 2026-08

