Home Benchmarks Learn Tools News
SPONSOR

AppSignal — Stop vibe-debugging. Every exception, every backtrace, grouped so you see patterns, not noise.

↗
Skills · Performance
Front-end skill · v2 · Core Web Vitals

Faster pages by default.

Runs inside your agent. Enforces Core Web Vitals targets, image and font discipline, JS budgets, lazy loading patterns.

Install the skill → Read SKILL.md
20
Performance patterns
6
AI tools supported
0
Dependencies
agent — frontend-performance live
$ agent write --skill frontend-performance Hero.tsx
▼ generating Hero.tsx with skill loaded
hero img has fetchpriority="high" USED
non-critical img with loading="lazy" USED
font-display: swap on @font-face USED
font preload for above-fold USED
JS bundle within budget USED
no render-blocking 3rd-party USED
content-visibility: auto on offscreen USED
written Hero.tsx · CWV passed · 0 anti-patterns A
→ ready to ship. open in editor? [Y/n]
Works with
  • Cursor
  • Claude Code
  • Codex CLI
  • Windsurf
  • GitHub Copilot
  • Gemini CLI
What it enforces

Core Web Vitals, baked into every page.

frontend-performance / patterns SKILL.md · Core Web Vitals
patterns/
image-discipline.diff •
−before.html Before
<!-- hero, but lazy and unsized -->
<img src="/hero.jpg" alt="..." loading="lazy">
 
<!-- below-fold, but eager -->
<img src="/photo.jpg" alt="...">
+after.html After
<picture>
<source srcset="/hero.avif" type="image/avif">
<img src="/hero.jpg" alt="..."
width="1200" height="600"
fetchpriority="high" decoding="async">
</picture>
 
<img src="/photo.avif" alt="..."
width="800" height="450"
loading="lazy" decoding="async">
− unsized hero · wrong priorities · slow LCP → + AVIF · sized · prioritized · fast LCP
font-loading.diff •
−fonts.css Before
/* invisible text until font loads */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2');
font-display: block;
}
 
<!-- no preload, late discovery -->
<link rel="stylesheet" href="/fonts.css">
+fonts.css After
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap;
size-adjust: 105%;
ascent-override: 90%;
descent-override: 20%;
}
 
<link rel="preload" as="font"
href="/fonts/custom.woff2"
type="font/woff2" crossorigin>
− FOIT · late discovery · font-swap CLS → + swap · preload · metrics matched · no jump
js-budgets.diff •
−app.html Before
<!-- blocks HTML parsing -->
<script src="/main.js"></script>
<script src="/analytics.js"></script>
 
// 800ms of work on the main thread
function processItems(items) {
for (const item of items) processItem(item);
}
+app.html After
<script src="/main.js" defer></script>
<script src="/analytics.js" async></script>
<script src="/app.js" type="module"></script>
 
async function processItems(items) {
for (const item of items) {
processItem(item);
await scheduler.yield();
}
}
− render-blocking · 800 ms long task → + deferred · modular · INP under 200 ms
cls-prevention.diff •
−layout.html Before
<img src="/photo.jpg" alt="...">
 
<iframe src="/embed"></iframe>
 
/* container collapses until ad loads */
.ad-slot { }
+layout.html After
<img src="/photo.avif" alt="..."
width="800" height="450"
loading="lazy" decoding="async">
 
.video-embed {
aspect-ratio: 16 / 9;
width: 100%;
}
 
.ad-slot { min-height: 250px; }
− unsized media · ad pops in · late shifts → + reserved space · aspect-ratio · CLS < 0.1
Benchmarked

Proof, not vibes.

Marketing Page brief · Claude Opus 4.7 · with skill vs. without · 2026-04-21
100
Performance 67 → 100 baseline +33
78
JS bundle savings vs baseline build −78%
12
Optimizations applied Avg per generated page
95
Perf grade · A vs C · 67 baseline +28
0–49 50–89 90–100
Single-run comparison. Same model and prompt, scored on Lighthouse + an internal rubric.
Install

One file. Six tools. Zero ceremony.

One Markdown file, zero dependencies. Pick your tool below.

1Drop this in

Project: .cursor/skills/frontend-performance.md

2Or fetch it directly
curl -fsSL https://webdeveloper.com/skills/frontend-performance/SKILL.md -o .cursor/skills/frontend-performance.md

Restart Cursor. Every page the agent writes from now on hits Core Web Vitals targets and avoids the listed anti-patterns.

1Drop this in

User-level: ~/.claude/skills/frontend-performance/SKILL.md

2Or fetch it directly
mkdir -p ~/.claude/skills/frontend-performance && curl -fsSL https://webdeveloper.com/skills/frontend-performance/SKILL.md -o ~/.claude/skills/frontend-performance/SKILL.md

Claude Code auto-discovers skills in ~/.claude/skills/. Available across every project on this machine.

1Drop this in

Project: AGENTS.md (append the SKILL contents)

2Or fetch it directly
curl -fsSL https://webdeveloper.com/skills/frontend-performance/SKILL.md >> AGENTS.md

Codex CLI reads AGENTS.md automatically when you run it from the project root.

1Drop this in

Project: .windsurf/rules/frontend-performance.md

2Or fetch it directly
mkdir -p .windsurf/rules && curl -fsSL https://webdeveloper.com/skills/frontend-performance/SKILL.md -o .windsurf/rules/frontend-performance.md

Windsurf loads project rules on every Cascade run.

1Drop this in

Project: .github/copilot-instructions.md (append)

2Or fetch it directly
mkdir -p .github && curl -fsSL https://webdeveloper.com/skills/frontend-performance/SKILL.md >> .github/copilot-instructions.md

Copilot reads .github/copilot-instructions.md as project-wide context.

1Drop this in

Project: .gemini/skills/frontend-performance.md

2Or fetch it directly
mkdir -p .gemini/skills && curl -fsSL https://webdeveloper.com/skills/frontend-performance/SKILL.md -o .gemini/skills/frontend-performance.md

Gemini CLI auto-loads project skills on the next run.

The full SKILL.md

385 lines · plain Markdown · MIT-licensed
SKILL.md
---
name: frontend-performance
description: >-
  Enforces Core Web Vitals optimization and loading performance patterns when
  building front-end pages. Use when adding images, fonts, scripts, stylesheets,
  or any resource loading, and when optimizing page speed or LCP/INP/CLS.
---

# Front-end Performance

Core Web Vitals are Google ranking factors. A slow page loses both users and
search position. Every pattern here directly impacts LCP, INP, or CLS.

**Fix in this order** — earlier items have more impact and often fix later
ones:

1. TTFB (server response time) — everything waits for this
2. LCP (what the user sees first)
3. CLS (visual stability)
4. INP (interaction responsiveness)

## Core Web Vitals Thresholds

| Metric | Good | Needs Improvement | Poor |
|--------|------|-------------------|------|
| LCP (Largest Contentful Paint) | <= 2.5s | 2.5s–4.0s | > 4.0s |
| INP (Interaction to Next Paint) | <= 200ms | 200ms–500ms | > 500ms |
| CLS (Cumulative Layout Shift) | <= 0.1 | 0.1–0.25 | > 0.25 |

These thresholds are measured at the 75th percentile of real user data.
Lab tools (Lighthouse) don't measure INP — you need field data (CrUX,
Web Vitals library) for that.

## LCP — Largest Contentful Paint

LCP measures how fast the largest visible element in the initial viewport
renders. It's usually a hero image, primary heading, or video poster.

**First step**: identify the LCP element. Open Chrome DevTools > Performance
panel > record a page load > look for the "LCP" marker. Everything below
assumes you know what the LCP element is.

### Preload the LCP image

The browser can't discover an image until it parses the HTML, finds the CSS,
and evaluates the `<img>` tag. Preloading short-circuits this:

```html
<link rel="preload" as="image" href="/hero.avif"
      fetchpriority="high" type="image/avif">
```

### Set fetchpriority on the LCP element

```html
<img src="/hero.avif" alt="..." fetchpriority="high"
     width="1200" height="600">
```

`fetchpriority="high"` tells the browser this resource is more important
than others at the same priority level. Only use it on 1-2 elements —
if everything is high priority, nothing is.

### Never lazy-load the LCP element

`loading="lazy"` defers loading until the element enters the viewport. For
the LCP element, this means the browser waits until it's already too late.
Only lazy-load images below the fold.

### Inline critical CSS

The CSS needed to render the LCP element should not be blocked behind a
render-blocking `<link>` stylesheet. For static sites, inline the critical
CSS in a `<style>` tag in `<head>`:

```html
<head>
  <style>
    /* Only styles needed for above-fold content */
  </style>
  <link rel="stylesheet" href="/styles.css" media="print" onload="this.media='all'">
</head>
```

The `media="print"` + `onload` pattern loads the full stylesheet
non-blocking while the inlined critical CSS handles the initial render.

### Preconnect to critical origins

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
```

**Limit to 2-3 origins.** Each preconnect opens a TCP connection and TLS
handshake. Too many preconnects waste bandwidth and can slow down the
connections that matter.

## INP — Interaction to Next Paint

INP measures the worst input delay across the entire page visit, not just
the first click. It captures the time from user input to the next frame
painted. 43% of origins fail the 200ms threshold.

### Break long tasks

Any JavaScript task over 50ms blocks the main thread, preventing the browser
from responding to user input:

```javascript
async function processItems(items) {
  for (const item of items) {
    processItem(item);
    if ('scheduler' in globalThis && 'yield' in scheduler) {
      await scheduler.yield();
    } else {
      await new Promise(r => setTimeout(r, 0));
    }
  }
}
```

`scheduler.yield()` gives the browser a chance to handle pending user
interactions between work chunks. The `setTimeout(0)` fallback covers
browsers without scheduler API support.

### Defer non-critical JavaScript

```html
<!-- Deferred: runs after HTML parsing, before DOMContentLoaded -->
<script src="/main.js" defer></script>

<!-- Async: runs as soon as loaded, independent of DOM -->
<script src="/analytics.js" async></script>

<!-- Module: deferred by default -->
<script src="/app.js" type="module"></script>
```

**Never** put a `<script>` in `<head>` without `defer`, `async`, or
`type="module"`. A bare `<script>` blocks HTML parsing entirely.

### Move work off the main thread

```javascript
requestIdleCallback(() => {
  initAnalytics();
  prefetchNextPage();
});
```

Use `requestIdleCallback` for non-urgent initialization. Use Web Workers for
CPU-intensive computation that doesn't need DOM access.

### Optimize event handlers

- **Avoid layout thrashing**: batch DOM reads before DOM writes
- **Debounce** scroll/resize handlers
- **Passive listeners** for touch/scroll:
  `addEventListener('scroll', handler, { passive: true })`
- Only use `event.preventDefault()` when necessary — it forces the event
  listener to be non-passive

## CLS — Cumulative Layout Shift

CLS measures unexpected visual movement after initial render. Every element
that shifts adds to the score. The threshold (0.1) is strict — even small
shifts accumulate.

### Always set dimensions on media

```html
<img src="/photo.avif" alt="..." width="800" height="450"
     loading="lazy" decoding="async">

<video width="640" height="360" poster="/thumb.jpg">
  <source src="/video.mp4" type="video/mp4">
</video>

<iframe width="560" height="315" src="..." loading="lazy"
        title="Video title"></iframe>
```

If exact dimensions are unknown, use `aspect-ratio`:

```css
.video-embed {
  aspect-ratio: 16 / 9;
  width: 100%;
}
```

### Prevent font-swap layout shift

```css
@font-face {
  font-family: 'CustomFont';
  src: url('/fonts/custom.woff2') format('woff2');
  font-display: swap;
  size-adjust: 105%;
  ascent-override: 90%;
  descent-override: 20%;
  line-gap-override: 0%;
}
```

`size-adjust` and the override descriptors match the fallback font's metrics
to the custom font. Without these, text reflows when the custom font loads,
causing layout shift. Getting these values right requires comparing the
fallback font metrics to the custom font — tools like
[Fallback Font Generator](https://screenspan.net/fallback) automate this.

### Reserve space for dynamic content

- Ads and embeds: set `min-height` on the container
- Skeleton screens: match the final layout dimensions
- Toasts and banners: use `position: fixed` or `transform`-based animation
  (transforms don't trigger layout shift)

## Images

### Modern formats with fallback

```html
<picture>
  <source srcset="/photo.avif" type="image/avif">
  <source srcset="/photo.webp" type="image/webp">
  <img src="/photo.jpg" alt="Descriptive alt text"
       width="800" height="450" loading="lazy" decoding="async">
</picture>
```

### Responsive images

```html
<img srcset="/photo-400.avif 400w,
            /photo-800.avif 800w,
            /photo-1200.avif 1200w"
     sizes="(max-width: 600px) 100vw,
            (max-width: 900px) 50vw,
            33vw"
     src="/photo-800.avif"
     alt="..." width="800" height="450"
     loading="lazy" decoding="async">
```

### Image rules (no exceptions)

| Attribute | LCP image | Below-fold images |
|-----------|-----------|-------------------|
| `loading` | Omit (default eager) | `lazy` |
| `decoding` | `async` | `async` |
| `fetchpriority` | `high` | Omit |
| `width` + `height` | Required | Required |
| Format | AVIF > WebP > JPEG | AVIF > WebP > JPEG |

## Fonts

```html
<link rel="preload" as="font" href="/fonts/main.woff2"
      type="font/woff2" crossorigin>
```

- Preload only the most critical font (1 file, maximum 2)
- Use `woff2` exclusively — universal support, best compression
- Subset to needed character ranges (Latin, etc.)
- Set `font-display: swap` — never `block` (invisible text during load)
- Self-host when possible to eliminate third-party connections

## Resource Hints

| Hint | What it does | When to use |
|------|-------------|-------------|
| `preconnect` | Opens connection to origin early | Critical third-party origins (fonts, CDN) |
| `preload` | Fetches resource at high priority | LCP image, critical font, critical CSS |
| `modulepreload` | Preloads ES module | Critical JS modules |
| `dns-prefetch` | Resolves DNS only | Non-critical third-party origins |

Don't use `prefetch` for resources needed on the current page — it has low
priority. Use `preload`.

## Speculation Rules API

Use Speculation Rules for instant-feeling navigations to likely-next pages:

```html
<script type="speculationrules">
{
  "prerender": [
    {
      "where": { "href_matches": "/news/*" },
      "eagerness": "moderate"
    }
  ],
  "prefetch": [
    {
      "where": { "selector_matches": "a[href^='/']" },
      "eagerness": "conservative"
    }
  ]
}
</script>
```

| Eagerness | Trigger | Use for |
|-----------|---------|---------|
| `conservative` | Pointer/touch down | Any internal link (low risk) |
| `moderate` | Hover for 200ms | Likely-next-page links |
| `eager` | Immediately | Almost-certain destinations (pagination next) |

**Default to `conservative` for prefetch and `moderate` for prerender.**
`eager` prerender consumes significant bandwidth and memory — only use it
when the navigation is near-certain.

## Performance Budgets

Set budgets and check them before every deploy:

| Resource | Budget | Rationale |
|----------|--------|-----------|
| Total page weight | < 500KB (compressed) | 3G connections transfer ~500KB in 2.5s |
| JavaScript (total) | < 200KB (compressed) | Parse + compile time on mid-range mobile |
| CSS (total) | < 50KB (compressed) | Render-blocking; every byte delays LCP |
| Hero image | < 100KB | Usually the LCP element |
| Web fonts | < 100KB (all files) | Render-blocking if not preloaded |
| Third-party scripts | < 100KB | Each one is code you don't control |

These are aggressive but achievable targets. A page that fits within them
will almost certainly pass Core Web Vitals.

## Measuring Performance

### Lab testing (during development)

- **Chrome DevTools Performance panel** — record a page load, identify the
  LCP element, find long tasks, check CLS sources
- **Lighthouse** — run in Incognito (no extensions), throttle to "Mobile"
  for realistic results. Scores below 90 need investigation.
- **WebPageTest** — most detailed waterfall analysis; use for diagnosing
  complex loading issues

### Field testing (real user data)

- **Chrome UX Report (CrUX)** — real-world CWV data from Chrome users,
  updated monthly. This is what Google uses for ranking.
- **web-vitals library** — add to your analytics to collect LCP, INP, CLS
  from real users:

```html
<script type="module">
import { onLCP, onINP, onCLS } from 'https://unpkg.com/web-vitals@4/dist/web-vitals.attribution.js?module';
onLCP(console.log);
onINP(console.log);
onCLS(console.log);
</script>
```

Lab and field data will differ. Lab tests use consistent conditions; field
data reflects real devices, networks, and user behavior. Both matter — use
lab for development, field for validation.

## Anti-Patterns

**Never do these:**

- Lazy-load the LCP image — delays the most important element
- `loading="eager"` on below-fold images — wastes bandwidth on content the
  user hasn't scrolled to
- More than 2-3 `preconnect` hints — excess connections slow down the ones
  that matter
- Render-blocking `<script>` in `<head>` without `defer`/`async` — blocks
  HTML parsing entirely
- Load full font families when using 1-2 weights — each unused font file
  is ~20-50KB wasted
- `font-display: block` — makes text invisible during font load (FOIT)
- Omit `width`/`height` on images — causes CLS
- Import full animation libraries (GSAP, Anime.js) for scroll effects —
  use CSS `animation-timeline` (see `frontend-css` skill)
- Load third-party scripts synchronously — they block the main thread and
  you don't control their response time
- `document.write()` — blocks parsing, can destroy the page on slow
  connections
- Serve raw JPEG/PNG when AVIF/WebP is available — 50-80% larger files
  for identical quality
Pair it

Stack it with the rest of the suite.

Front-end01 Modern CSS

Native nesting, container queries, :has(), CSS layers, scroll-driven animations, oklch colors, logical properties, subgrid, anchor positioning.

↗
Front-end02 Components

Semantic HTML, ARIA widget patterns, native dialog and Popover API, heading hierarchy, progressive enhancement, BEM naming.

↗
Review07 Pre-Deploy Review

26-point audit of error handling, debug artifacts, hallucinated APIs, and a11y smells before you ship.

↗

Changelog

V2 April 9, 2026
Added performance budgets table, measurement section with lab and field tools, web-vitals library integration, LCP image rules comparison table, Speculation Rules eagerness guidance, critical CSS loading pattern, and cross-references to CSS and SEO skills.
V1 April 3, 2026
Initial skill covering Core Web Vitals thresholds, LCP/INP/CLS optimization patterns, image formats, font loading, resource hints, Speculation Rules API, and script loading.

FAQ

What Core Web Vitals does this skill optimize?

This skill targets all three Core Web Vitals that Google uses for search ranking: LCP (Largest Contentful Paint, target <= 2.5s), INP (Interaction to Next Paint, target <= 200ms), and CLS (Cumulative Layout Shift, target <= 0.1). It provides specific patterns for each metric.

How does this skill handle image optimization?

The skill enforces AVIF-first with WebP fallback using <picture> elements, responsive images with srcset and sizes, lazy loading for below-fold images, fetchpriority="high" on the LCP image, required width and height attributes to prevent layout shift, and decoding="async" on all images.

What is the Speculation Rules API?

The Speculation Rules API lets browsers prerender or prefetch pages the user is likely to navigate to next, providing near-instant page transitions. This skill includes patterns for configuring speculation rules with different eagerness levels — conservative (on pointer down), moderate (on hover), and eager (immediate).

Which AI coding tools is this compatible with?

Cursor, Claude Code, Codex CLI, Windsurf, GitHub Copilot, and Gemini CLI. The skill is a single Markdown file and ships in the native format for each tool with one-click copy.

STATUS ● BUILDING THE FUTURE
MISSION MAKE AI SHIP BETTER CODE.
VERSION BETA 3.0

MAKE AI SHIP BETTER CODE.

@WEBDEVELOPERHQ ↗
TERMS / PRIVACY
FRIENDS
Authentic Jobs ↗
Web Reference ↗
Ready.dev ↗
Fullres ↗
© 2026 WEB DEVELOPER / ALL RIGHTS RESERVED