Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A curated collection of JavaScript snippets to measure and debug Web Performance

[![CI](https://github.com/nucliweb/webperf-snippets/actions/workflows/ci.yml/badge.svg)](https://github.com/nucliweb/webperf-snippets/actions/workflows/ci.yml)
[![Release](https://img.shields.io/github/v/release/nucliweb/webperf-snippets)](https://github.com/nucliweb/webperf-snippets/releases)
[![Snippets](https://img.shields.io/badge/snippets-47-0f766e)](https://webperf-snippets.nucliweb.net)
[![Snippets](https://img.shields.io/badge/snippets-48-0f766e)](https://webperf-snippets.nucliweb.net)
[![License](https://img.shields.io/github/license/nucliweb/webperf-snippets)](./LICENSE)
[![Star History](https://img.shields.io/github/stars/nucliweb/webperf-snippets?style=social)](https://star-history.com/#nucliweb/webperf-snippets&Date)

Expand Down Expand Up @@ -87,9 +87,9 @@ This installs skills to `~/.claude/skills/` for use across any project.

| Skill | Snippets | Description |
| ------------------------- | -------- | ---------------------------------------------------------------- |
| `webperf` | 47 | Main entry point for all web performance analysis |
| `webperf` | 48 | Main entry point for all web performance analysis |
| `webperf-core-web-vitals` | 7 | LCP, CLS, INP measurements with detailed breakdowns |
| `webperf-loading` | 28 | TTFB, FCP, script/font analysis, resource hints, service workers |
| `webperf-loading` | 29 | TTFB, FCP, script/font analysis, resource hints, service workers |
| `webperf-interaction` | 8 | Long tasks, animation frames, scroll jank, INP debugging |
| `webperf-media` | 3 | Image/video audits, lazy loading validation, SVG analysis |
| `webperf-resources` | 1 | Network bandwidth, connection quality, adaptive loading |
Expand Down
6 changes: 3 additions & 3 deletions SKILLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A collection of [Agent Skills](https://agentskills.io/) for measuring and debugg

## Why WebPerf Skills?

These skills transform 47 battle-tested JavaScript snippets into agent capabilities for any skills-compatible AI coding assistant:
These skills transform 48 battle-tested JavaScript snippets into agent capabilities for any skills-compatible AI coding assistant:

- **Browser Console Integration**: Run performance measurements directly in Chrome DevTools
- **Real-time Analysis**: Measure actual user experience on live pages
Expand All @@ -22,7 +22,7 @@ These skills transform 47 battle-tested JavaScript snippets into agent capabilit
| ------------------------------------------------------- | ---------- | ------------------------------------------------------------ |
| **[webperf](#webperf)** | Meta-skill | "Audit performance", "check web vitals", "analyze this page" |
| **[webperf-core-web-vitals](#webperf-core-web-vitals)** | 7 | "Debug LCP", "check CLS", "measure INP" |
| **[webperf-loading](#webperf-loading)** | 28 | "Analyze TTFB", "check render-blocking", "audit scripts" |
| **[webperf-loading](#webperf-loading)** | 29 | "Analyze TTFB", "check render-blocking", "audit scripts" |
| **[webperf-interaction](#webperf-interaction)** | 8 | "Debug jank", "long tasks", "animation frames" |
| **[webperf-media](#webperf-media)** | 3 | "Audit images", "optimize video", "lazy loading" |
| **[webperf-resources](#webperf-resources)** | 1 | "Check bandwidth", "network quality" |
Expand Down Expand Up @@ -105,7 +105,7 @@ The main entry point that helps identify the right skill for your performance qu
**What it does:**

- Routes to the appropriate specialized skill
- Provides overview of all 47 available snippets
- Provides overview of all 48 available snippets
- Suggests which skill to use based on your question

### webperf-core-web-vitals
Expand Down
98 changes: 98 additions & 0 deletions pages/DevTools-Overrides/Fetch-XHR-Timeline.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import injectSnippet from '../../snippets/DevTools-Overrides/Fetch-XHR-Timeline-inject.js?raw'
import readSnippet from '../../snippets/DevTools-Overrides/Fetch-XHR-Timeline-read.js?raw'
import { Snippet } from '../../components/Snippet'

# Fetch & XHR Timeline

### Overview

Intercepts every `fetch()` and `XMLHttpRequest` call made during the page lifecycle and correlates them with the [Largest Contentful Paint (LCP)](https://web.dev/articles/lcp) timing.

Console snippets can only see requests that have already completed and are visible in the Resource Timing API. That excludes cross-origin requests without `Timing-Allow-Origin`, requests made by service workers, and calls that fail before a response is received. This snippet patches `window.fetch` and `XMLHttpRequest.prototype` at page initialization, so every call is captured regardless of origin or outcome.

**What it helps diagnose:**

- API calls that complete *before* LCP and may be blocking the critical rendering path
- Failed or errored requests during framework bootstrap (Angular, React, Vue resolvers and guards)
- Unnecessary authentication checks on pages where the user is known to be unauthenticated
- The full request inventory of a page, including calls invisible to the Network panel timing data

> This snippet requires [DevTools Overrides](/DevTools-Overrides). Read [What are DevTools Overrides?](/DevTools-Overrides) for setup instructions.

---

### Part 1 — Inject snippet

Add this inside a `<script>` tag before the closing `</head>` of the overridden HTML file. It runs before any other script and stores all captured calls in `window.__perfCalls`.

<Snippet code={injectSnippet} />

---

### Part 2 — Read snippet

Run this in the console after the page has loaded to see the captured data correlated with LCP.

<Snippet code={readSnippet} />

---

### Understanding the Results

**Summary section:**

| Field | Meaning |
|-------|---------|
| Total calls | All fetch and XHR calls captured from page start |
| Before LCP | Calls that completed before LCP — these are candidates for critical path investigation |
| After LCP | Calls confirmed not to have blocked LCP |
| Errors / failed calls | Calls with HTTP 4xx/5xx status or network errors |

**Before LCP table:**

Calls in this group completed before the LCP element was painted. They *may* have contributed to the rendering delay — particularly if a framework waits for their response before rendering content. Cross-reference with the LCP element: if it is rendered by a JavaScript framework, any call completing shortly before LCP is a strong candidate.

**Failed calls table:**

Failed calls during bootstrap are a common source of unexpected rendering delays. A framework resolver that catches an auth error and redirects, or a config endpoint that times out, can add hundreds of milliseconds before the page renders its first meaningful content.

---

### How `window.__perfCalls` works

The inject snippet stores all captured calls in `window.__perfCalls` as an array. You can query it directly at any point from the console:

```js
// All calls
console.table(window.__perfCalls)

// Only failed calls
window.__perfCalls.filter(c => c.status === 'ERROR' || c.status >= 400)

// Only calls that started in the first 2 seconds
window.__perfCalls.filter(c => c.start < 2000)
```

Each entry has the following shape:

```js
{
type: "fetch" | "xhr",
url: "https://...",
start: 312, // ms from page start
end: 748, // ms from page start
duration: 436, // ms
status: 200, // HTTP status code, or "ERROR"
error: "...", // only present on network errors
}
```

---

### Further Reading

- [Largest Contentful Paint (LCP)](https://web.dev/articles/lcp) | web.dev
- [LCP Sub-Parts](/CoreWebVitals/LCP-Sub-Parts) | Detailed LCP phase breakdown
- [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) | MDN
- [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) | MDN
- [Resource Timing API](https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Resource_timing) | MDN
8 changes: 8 additions & 0 deletions pages/DevTools-Overrides/_meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"index": {
"title": "What are DevTools Overrides?"
},
"Fetch-XHR-Timeline": {
"title": "Fetch & XHR Timeline"
}
}
67 changes: 67 additions & 0 deletions pages/DevTools-Overrides/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# DevTools Overrides

### What are DevTools Overrides?

**DevTools Overrides** is a Chrome DevTools feature that lets you replace any resource served by a website — HTML, JavaScript, CSS — with a local version stored on your machine. The substitution happens transparently on every page load, without touching the server or the source code.

You can find it under **Sources → Overrides** in Chrome DevTools.

> **Cannot be automated.** Unlike the snippets in other categories, Overrides require manual setup in Chrome DevTools. They cannot be executed programmatically via automation tools such as Puppeteer, Playwright, or the DevTools Protocol.

---

### Why a dedicated category?

The snippets in this collection run in the browser console *after* the page has loaded. That works for measuring what already happened, but some problems only reveal themselves *during* the page lifecycle — before frameworks bootstrap, before API calls fire, before the first paint.

DevTools Overrides let you inject instrumentation code **before any other script runs**, making it possible to observe things that are invisible to console snippets:

- Every `fetch()` and `XHR` call from the moment the page starts loading
- Framework initialization sequences
- Errors thrown during bootstrap that disappear before the console opens
- Timing of API calls relative to Core Web Vitals like LCP

---

### How to set up an Override

**Step 1 — Enable local overrides**

Open Chrome DevTools → **Sources** → **Overrides** tab → click **Select folder for overrides** and choose a local directory. When prompted, allow DevTools to access it.

**Step 2 — Save the resource you want to modify**

Navigate to the page, open **Sources** → **Page**, find the HTML file or script you want to override, right-click it, and select **Save for overrides**. A purple dot will appear next to the file indicating it is now overridden locally.

**Step 3 — Inject the snippet**

Open the local copy of the file in DevTools and add the inject snippet code inside a `<script>` tag before the closing `</head>`:

```html
<script>
// paste inject snippet here
</script>
```

**Step 4 — Reload and read**

Reload the page. The override fires before any other script. Once the page has loaded, run the companion **read snippet** from the console to see the captured data.

---

### Workflow

```
Setup (once) Every page load Console (on demand)
───────────── ──────────────── ───────────────────
Enable Overrides → Inject snippet runs → Paste read snippet
Save HTML file Captures data into See results correlated
Add <script> tag window.__perfCalls with LCP and other metrics
```

---

### Further Reading

- [Local Overrides](https://developer.chrome.com/docs/devtools/overrides) | Chrome Developers
- [Override web content and HTTP response headers](https://developer.chrome.com/docs/devtools/overrides) | Chrome Developers
3 changes: 3 additions & 0 deletions pages/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@
},
"Media": {
"title": "Media"
},
"DevTools-Overrides": {
"title": "DevTools Overrides"
}
}
2 changes: 1 addition & 1 deletion scripts/check-consistency.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ function verifyPublishedCounts(errors) {
ok:
skillsDoc.includes(`These skills transform ${total} battle-tested JavaScript snippets`) &&
skillsDoc.includes(`| **[webperf-loading](#webperf-loading)** | ${categoryCounts.Loading}`) &&
skillsDoc.includes('Provides overview of all 47 available snippets'),
skillsDoc.includes(`Provides overview of all ${total} available snippets`),
},
{
file: 'skills/webperf/SKILL.md',
Expand Down
4 changes: 2 additions & 2 deletions skills/webperf/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ metadata:

# WebPerf Snippets Toolkit

A collection of 47 JavaScript snippets for measuring and debugging web performance in Chrome DevTools. Each snippet runs in the browser console and outputs structured, color-coded results.
A collection of 48 JavaScript snippets for measuring and debugging web performance in Chrome DevTools. Each snippet runs in the browser console and outputs structured, color-coded results.

## Quick Reference

| Skill | Snippets | Trigger phrases |
|-------|----------|-----------------|
| webperf-core-web-vitals | 7 | "debug LCP", "slow LCP", "CLS", "layout shifts", "INP", "interaction latency", "responsiveness" |
| webperf-loading | 28 | "TTFB", "slow server", "FCP", "render blocking", "font loading", "script loading", "resource hints", "service worker" |
| webperf-loading | 29 | "TTFB", "slow server", "FCP", "render blocking", "font loading", "script loading", "resource hints", "service worker" |
| webperf-interaction | 8 | "jank", "scroll performance", "long tasks", "animation frames", "INP debug" |
| webperf-media | 3 | "image audit", "lazy loading", "image optimization", "video audit" |
| webperf-resources | 1 | "network quality", "bandwidth", "connection type", "save-data" |
Expand Down
73 changes: 73 additions & 0 deletions snippets/DevTools-Overrides/Fetch-XHR-Timeline-inject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Fetch & XHR Timeline — Inject via DevTools Overrides
// https://webperf-snippets.nucliweb.net
(() => {
const calls = [];

// Intercept fetch
const origFetch = window.fetch;
window.fetch = function (...args) {
const url = typeof args[0] === "string" ? args[0] : args[0]?.url;
const start = performance.now();
return origFetch.apply(this, args).then((r) => {
const end = performance.now();
calls.push({
type: "fetch",
url,
start: Math.round(start),
end: Math.round(end),
duration: Math.round(end - start),
status: r.status,
});
return r;
}).catch((err) => {
const end = performance.now();
calls.push({
type: "fetch",
url,
start: Math.round(start),
end: Math.round(end),
duration: Math.round(end - start),
status: "ERROR",
error: err.message,
});
throw err;
});
};

// Intercept XHR
const origOpen = XMLHttpRequest.prototype.open;
const origSend = XMLHttpRequest.prototype.send;

XMLHttpRequest.prototype.open = function (method, url) {
this.__url = url;
this.__method = method;
return origOpen.apply(this, arguments);
};

XMLHttpRequest.prototype.send = function () {
const start = performance.now();
this.addEventListener("loadend", () => {
const end = performance.now();
calls.push({
type: "xhr",
url: this.__url,
start: Math.round(start),
end: Math.round(end),
duration: Math.round(end - start),
status: this.status || "ERROR",
});
});
return origSend.apply(this, arguments);
};

window.__perfCalls = calls;

// Capture LCP for correlation in the read snippet
try {
new PerformanceObserver((list) => {
const entries = list.getEntries();
const last = entries[entries.length - 1];
window.__lcpTime = Math.round(last.startTime);
}).observe({ type: "largest-contentful-paint", buffered: true });
} catch {}
})();
Loading
Loading