|
| 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 |
0 commit comments