Skip to content

Commit bbb5cb7

Browse files
committed
Ignore TTFB for loads where responseStart is zero
1 parent 7f0ed0b commit bbb5cb7

File tree

3 files changed

+55
-9
lines changed

3 files changed

+55
-9
lines changed

src/onTTFB.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,28 @@ export const onTTFB = (onReport: ReportCallback, opts?: ReportOpts) => {
6767
const navEntry = getNavigationEntry();
6868

6969
if (navEntry) {
70-
// The activationStart reference is used because TTFB should be
71-
// relative to page activation rather than navigation start if the
72-
// page was prerendered. But in cases where `activationStart` occurs
73-
// after the first byte is received, this time should be clamped at 0.
74-
metric.value = Math.max(navEntry.responseStart - getActivationStart(), 0);
70+
const responseStart = navEntry.responseStart;
7571

7672
// In some cases the value reported is negative or is larger
7773
// than the current page time. Ignore these cases:
7874
// https://github.com/GoogleChrome/web-vitals/issues/137
7975
// https://github.com/GoogleChrome/web-vitals/issues/162
80-
if (metric.value < 0 || metric.value > performance.now()) return;
76+
if (responseStart < 0 || responseStart > performance.now()) return;
8177

82-
metric.entries = [navEntry];
78+
// If the navigation entry's `responseStart` value is 0, ignore it.
79+
// This likely means the request included a cross-origin redirect, and
80+
// the browser has removed timing info for privacy/security reasons.
81+
// See: https://github.com/GoogleChrome/web-vitals/issues/275
82+
if (responseStart > 0) {
83+
// The activationStart reference is used because TTFB should be
84+
// relative to page activation rather than navigation start if the
85+
// page was prerendered. But in cases where `activationStart` occurs
86+
// after the first byte is received, this time should be clamped at 0.
87+
metric.value = Math.max(responseStart - getActivationStart(), 0);
8388

84-
report(true);
89+
metric.entries = [navEntry];
90+
report(true);
91+
}
8592

8693
// Only report TTFB after bfcache restores if a `navigation` entry
8794
// was reported for the initial load.

test/e2e/onTTFB-test.js

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import assert from 'assert';
1818
import {beaconCountIs, clearBeacons, getBeacons} from '../utils/beacons.js';
19+
import {domReadyState} from '../utils/domReadyState.js';
1920
import {stubForwardBack} from '../utils/stubForwardBack.js';
2021

2122

@@ -175,15 +176,41 @@ describe('onTTFB()', async function() {
175176

176177
const ttfb2 = await getTTFBBeacon();
177178

178-
assert(ttfb2.value >= 0);
179179
assert(ttfb2.id.match(/^v3-\d+-\d+$/));
180+
assert.strictEqual(ttfb2.value, 0);
180181
assert.strictEqual(ttfb2.name, 'TTFB');
181182
assert.strictEqual(ttfb2.value, ttfb2.delta);
182183
assert.strictEqual(ttfb2.rating, 'good');
183184
assert.strictEqual(ttfb2.navigationType, 'back-forward-cache');
184185
assert.strictEqual(ttfb2.entries.length, 0);
185186
});
186187

188+
it('ignores navigations with no responseStart timestamp', async function() {
189+
await browser.url('/test/ttfb?responseStart=0');
190+
191+
await domReadyState('complete');
192+
193+
// Wait a bit to ensure no beacons were sent.
194+
await browser.pause(1000);
195+
196+
const loadBeacons = await getBeacons();
197+
assert.strictEqual(loadBeacons.length, 0);
198+
199+
// Test back-forward navigations to ensure they're sent, even if the
200+
// initial page TTFB value is ignored.
201+
await stubForwardBack();
202+
203+
const ttfb = await getTTFBBeacon();
204+
205+
assert(ttfb.id.match(/^v3-\d+-\d+$/));
206+
assert.strictEqual(ttfb.value, 0);
207+
assert.strictEqual(ttfb.name, 'TTFB');
208+
assert.strictEqual(ttfb.value, ttfb.delta);
209+
assert.strictEqual(ttfb.rating, 'good');
210+
assert.strictEqual(ttfb.navigationType, 'back-forward-cache');
211+
assert.strictEqual(ttfb.entries.length, 0);
212+
});
213+
187214
describe('attribution', function() {
188215
it('includes attribution data on the metric object', async function() {
189216
await browser.url('/test/ttfb?attribution=1');

test/views/ttfb.njk

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@
2424

2525
<p><a id="navigate-away" href="https://example.com">Navigate away</a></p>
2626

27+
<script>
28+
// Set the blocking values based on query params if present.
29+
const params = new URLSearchParams(location.search);
30+
31+
if (params.has('responseStart')) {
32+
const navEntry = performance.getEntriesByType('navigation')[0];
33+
Object.defineProperty(navEntry, 'responseStart', {
34+
value: Number(params.get('responseStart')),
35+
});
36+
}
37+
</script>
38+
2739
<script type="module">
2840
import {onTTFB} from '{{ modulePath }}';
2941

0 commit comments

Comments
 (0)