Skip to content

Commit 6f7bd84

Browse files
authored
Merge pull request #71 from nucliweb/feat/devtools-overrides-fetch-xhr-timeline
feat: add Fetch & XHR Timeline DevTools Override snippet
2 parents 7012a1f + 77d47e3 commit 6f7bd84

File tree

10 files changed

+449
-9
lines changed

10 files changed

+449
-9
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ A curated collection of JavaScript snippets to measure and debug Web Performance
44

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

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

8888
| Skill | Snippets | Description |
8989
| ------------------------- | -------- | ---------------------------------------------------------------- |
90-
| `webperf` | 47 | Main entry point for all web performance analysis |
90+
| `webperf` | 48 | Main entry point for all web performance analysis |
9191
| `webperf-core-web-vitals` | 7 | LCP, CLS, INP measurements with detailed breakdowns |
92-
| `webperf-loading` | 28 | TTFB, FCP, script/font analysis, resource hints, service workers |
92+
| `webperf-loading` | 29 | TTFB, FCP, script/font analysis, resource hints, service workers |
9393
| `webperf-interaction` | 8 | Long tasks, animation frames, scroll jank, INP debugging |
9494
| `webperf-media` | 3 | Image/video audits, lazy loading validation, SVG analysis |
9595
| `webperf-resources` | 1 | Network bandwidth, connection quality, adaptive loading |

SKILLS.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ A collection of [Agent Skills](https://agentskills.io/) for measuring and debugg
66

77
## Why WebPerf Skills?
88

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

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

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

111111
### webperf-core-web-vitals
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import injectSnippet from '../../snippets/DevTools-Overrides/Fetch-XHR-Timeline-inject.js?raw'
2+
import readSnippet from '../../snippets/DevTools-Overrides/Fetch-XHR-Timeline-read.js?raw'
3+
import { Snippet } from '../../components/Snippet'
4+
5+
# Fetch & XHR Timeline
6+
7+
### Overview
8+
9+
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.
10+
11+
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.
12+
13+
**What it helps diagnose:**
14+
15+
- API calls that complete *before* LCP and may be blocking the critical rendering path
16+
- Failed or errored requests during framework bootstrap (Angular, React, Vue resolvers and guards)
17+
- Unnecessary authentication checks on pages where the user is known to be unauthenticated
18+
- The full request inventory of a page, including calls invisible to the Network panel timing data
19+
20+
> This snippet requires [DevTools Overrides](/DevTools-Overrides). Read [What are DevTools Overrides?](/DevTools-Overrides) for setup instructions.
21+
22+
---
23+
24+
### Part 1 — Inject snippet
25+
26+
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`.
27+
28+
<Snippet code={injectSnippet} />
29+
30+
---
31+
32+
### Part 2 — Read snippet
33+
34+
Run this in the console after the page has loaded to see the captured data correlated with LCP.
35+
36+
<Snippet code={readSnippet} />
37+
38+
---
39+
40+
### Understanding the Results
41+
42+
**Summary section:**
43+
44+
| Field | Meaning |
45+
|-------|---------|
46+
| Total calls | All fetch and XHR calls captured from page start |
47+
| Before LCP | Calls that completed before LCP — these are candidates for critical path investigation |
48+
| After LCP | Calls confirmed not to have blocked LCP |
49+
| Errors / failed calls | Calls with HTTP 4xx/5xx status or network errors |
50+
51+
**Before LCP table:**
52+
53+
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.
54+
55+
**Failed calls table:**
56+
57+
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.
58+
59+
---
60+
61+
### How `window.__perfCalls` works
62+
63+
The inject snippet stores all captured calls in `window.__perfCalls` as an array. You can query it directly at any point from the console:
64+
65+
```js
66+
// All calls
67+
console.table(window.__perfCalls)
68+
69+
// Only failed calls
70+
window.__perfCalls.filter(c => c.status === 'ERROR' || c.status >= 400)
71+
72+
// Only calls that started in the first 2 seconds
73+
window.__perfCalls.filter(c => c.start < 2000)
74+
```
75+
76+
Each entry has the following shape:
77+
78+
```js
79+
{
80+
type: "fetch" | "xhr",
81+
url: "https://...",
82+
start: 312, // ms from page start
83+
end: 748, // ms from page start
84+
duration: 436, // ms
85+
status: 200, // HTTP status code, or "ERROR"
86+
error: "...", // only present on network errors
87+
}
88+
```
89+
90+
---
91+
92+
### Further Reading
93+
94+
- [Largest Contentful Paint (LCP)](https://web.dev/articles/lcp) | web.dev
95+
- [LCP Sub-Parts](/CoreWebVitals/LCP-Sub-Parts) | Detailed LCP phase breakdown
96+
- [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) | MDN
97+
- [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) | MDN
98+
- [Resource Timing API](https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Resource_timing) | MDN
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"index": {
3+
"title": "What are DevTools Overrides?"
4+
},
5+
"Fetch-XHR-Timeline": {
6+
"title": "Fetch & XHR Timeline"
7+
}
8+
}

pages/DevTools-Overrides/index.mdx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# DevTools Overrides
2+
3+
### What are DevTools Overrides?
4+
5+
**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.
6+
7+
You can find it under **Sources → Overrides** in Chrome DevTools.
8+
9+
> **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.
10+
11+
---
12+
13+
### Why a dedicated category?
14+
15+
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.
16+
17+
DevTools Overrides let you inject instrumentation code **before any other script runs**, making it possible to observe things that are invisible to console snippets:
18+
19+
- Every `fetch()` and `XHR` call from the moment the page starts loading
20+
- Framework initialization sequences
21+
- Errors thrown during bootstrap that disappear before the console opens
22+
- Timing of API calls relative to Core Web Vitals like LCP
23+
24+
---
25+
26+
### How to set up an Override
27+
28+
**Step 1 — Enable local overrides**
29+
30+
Open Chrome DevTools → **Sources****Overrides** tab → click **Select folder for overrides** and choose a local directory. When prompted, allow DevTools to access it.
31+
32+
**Step 2 — Save the resource you want to modify**
33+
34+
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.
35+
36+
**Step 3 — Inject the snippet**
37+
38+
Open the local copy of the file in DevTools and add the inject snippet code inside a `<script>` tag before the closing `</head>`:
39+
40+
```html
41+
<script>
42+
// paste inject snippet here
43+
</script>
44+
```
45+
46+
**Step 4 — Reload and read**
47+
48+
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.
49+
50+
---
51+
52+
### Workflow
53+
54+
```
55+
Setup (once) Every page load Console (on demand)
56+
───────────── ──────────────── ───────────────────
57+
Enable Overrides → Inject snippet runs → Paste read snippet
58+
Save HTML file Captures data into See results correlated
59+
Add <script> tag window.__perfCalls with LCP and other metrics
60+
```
61+
62+
---
63+
64+
### Further Reading
65+
66+
- [Local Overrides](https://developer.chrome.com/docs/devtools/overrides) | Chrome Developers
67+
- [Override web content and HTTP response headers](https://developer.chrome.com/docs/devtools/overrides) | Chrome Developers

pages/_meta.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,8 @@
1616
},
1717
"Media": {
1818
"title": "Media"
19+
},
20+
"DevTools-Overrides": {
21+
"title": "DevTools Overrides"
1922
}
2023
}

scripts/check-consistency.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ function verifyPublishedCounts(errors) {
203203
ok:
204204
skillsDoc.includes(`These skills transform ${total} battle-tested JavaScript snippets`) &&
205205
skillsDoc.includes(`| **[webperf-loading](#webperf-loading)** | ${categoryCounts.Loading}`) &&
206-
skillsDoc.includes('Provides overview of all 47 available snippets'),
206+
skillsDoc.includes(`Provides overview of all ${total} available snippets`),
207207
},
208208
{
209209
file: 'skills/webperf/SKILL.md',

skills/webperf/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ metadata:
1313

1414
# WebPerf Snippets Toolkit
1515

16-
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.
16+
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.
1717

1818
## Quick Reference
1919

2020
| Skill | Snippets | Trigger phrases |
2121
|-------|----------|-----------------|
2222
| webperf-core-web-vitals | 7 | "debug LCP", "slow LCP", "CLS", "layout shifts", "INP", "interaction latency", "responsiveness" |
23-
| webperf-loading | 28 | "TTFB", "slow server", "FCP", "render blocking", "font loading", "script loading", "resource hints", "service worker" |
23+
| webperf-loading | 29 | "TTFB", "slow server", "FCP", "render blocking", "font loading", "script loading", "resource hints", "service worker" |
2424
| webperf-interaction | 8 | "jank", "scroll performance", "long tasks", "animation frames", "INP debug" |
2525
| webperf-media | 3 | "image audit", "lazy loading", "image optimization", "video audit" |
2626
| webperf-resources | 1 | "network quality", "bandwidth", "connection type", "save-data" |
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Fetch & XHR Timeline — Inject via DevTools Overrides
2+
// https://webperf-snippets.nucliweb.net
3+
(() => {
4+
const calls = [];
5+
6+
// Intercept fetch
7+
const origFetch = window.fetch;
8+
window.fetch = function (...args) {
9+
const url = typeof args[0] === "string" ? args[0] : args[0]?.url;
10+
const start = performance.now();
11+
return origFetch.apply(this, args).then((r) => {
12+
const end = performance.now();
13+
calls.push({
14+
type: "fetch",
15+
url,
16+
start: Math.round(start),
17+
end: Math.round(end),
18+
duration: Math.round(end - start),
19+
status: r.status,
20+
});
21+
return r;
22+
}).catch((err) => {
23+
const end = performance.now();
24+
calls.push({
25+
type: "fetch",
26+
url,
27+
start: Math.round(start),
28+
end: Math.round(end),
29+
duration: Math.round(end - start),
30+
status: "ERROR",
31+
error: err.message,
32+
});
33+
throw err;
34+
});
35+
};
36+
37+
// Intercept XHR
38+
const origOpen = XMLHttpRequest.prototype.open;
39+
const origSend = XMLHttpRequest.prototype.send;
40+
41+
XMLHttpRequest.prototype.open = function (method, url) {
42+
this.__url = url;
43+
this.__method = method;
44+
return origOpen.apply(this, arguments);
45+
};
46+
47+
XMLHttpRequest.prototype.send = function () {
48+
const start = performance.now();
49+
this.addEventListener("loadend", () => {
50+
const end = performance.now();
51+
calls.push({
52+
type: "xhr",
53+
url: this.__url,
54+
start: Math.round(start),
55+
end: Math.round(end),
56+
duration: Math.round(end - start),
57+
status: this.status || "ERROR",
58+
});
59+
});
60+
return origSend.apply(this, arguments);
61+
};
62+
63+
window.__perfCalls = calls;
64+
65+
// Capture LCP for correlation in the read snippet
66+
try {
67+
new PerformanceObserver((list) => {
68+
const entries = list.getEntries();
69+
const last = entries[entries.length - 1];
70+
window.__lcpTime = Math.round(last.startTime);
71+
}).observe({ type: "largest-contentful-paint", buffered: true });
72+
} catch {}
73+
})();

0 commit comments

Comments
 (0)