Skip to content

Commit 71015f0

Browse files
authored
fix: yield main thread before navigating (#12225)
* fix: yield main thread before navigating * Update packages/kit/src/runtime/client/client.js * gah * add test * bigger magic number
1 parent b10bd44 commit 71015f0

File tree

5 files changed

+47
-1
lines changed

5 files changed

+47
-1
lines changed

.changeset/pink-tigers-whisper.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@sveltejs/kit': patch
3+
---
4+
5+
fix: yield main thread before navigating

packages/kit/src/runtime/client/client.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2027,7 +2027,7 @@ function _start_router() {
20272027
}
20282028

20292029
/** @param {MouseEvent} event */
2030-
container.addEventListener('click', (event) => {
2030+
container.addEventListener('click', async (event) => {
20312031
// Adapted from https://github.com/visionmedia/page.js
20322032
// MIT license https://github.com/visionmedia/page.js#license
20332033
if (event.button || event.which !== 1) return;
@@ -2120,6 +2120,16 @@ function _start_router() {
21202120

21212121
event.preventDefault();
21222122

2123+
// allow the browser to repaint before navigating —
2124+
// this prevents INP scores being penalised
2125+
await new Promise((fulfil) => {
2126+
requestAnimationFrame(() => {
2127+
setTimeout(fulfil, 0);
2128+
});
2129+
2130+
setTimeout(fulfil, 100); // fallback for edge case where rAF doesn't fire because e.g. tab was backgrounded
2131+
});
2132+
21232133
navigate({
21242134
type: 'link',
21252135
url,

packages/kit/test/apps/basics/src/routes/routing/+page.svelte

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
<a href="/routing/a">a</a>
88
<a href="/routing/ambiguous/ok.json" rel="external">ok</a>
9+
<a href="/routing/next-paint">next-paint</a>
910
<a href="/routing/symlink-from">symlinked</a>
1011
<a href="http://localhost:{$page.url.searchParams.get('port')}">elsewhere</a>
1112
<a href="/static.json">static.json</a>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p>next-paint</p>

packages/kit/test/apps/basics/test/client.test.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,3 +1163,32 @@ test.describe('reroute', () => {
11631163
expect(await page.textContent('h1')).toContain('Full Navigation');
11641164
});
11651165
});
1166+
1167+
test.describe('INP', () => {
1168+
test('does not block next paint', async ({ page }) => {
1169+
// Thanks to https://publishing-project.rivendellweb.net/measuring-performance-tasks-with-playwright/#interaction-to-next-paint-inp
1170+
async function measureInteractionToPaint(selector) {
1171+
return page.evaluate(async (selector) => {
1172+
return new Promise((resolve) => {
1173+
const startTime = performance.now();
1174+
document.querySelector(selector).click();
1175+
requestAnimationFrame(() => {
1176+
const endTime = performance.now();
1177+
resolve(endTime - startTime);
1178+
});
1179+
});
1180+
}, selector);
1181+
}
1182+
1183+
await page.goto('/routing');
1184+
1185+
const client = await page.context().newCDPSession(page);
1186+
await client.send('Emulation.setCPUThrottlingRate', { rate: 100 });
1187+
1188+
const time = await measureInteractionToPaint('a[href="/routing/next-paint"]');
1189+
1190+
// we may need to tweak this number, and the `rate` above,
1191+
// depending on if this proves flaky
1192+
expect(time).toBeLessThan(400);
1193+
});
1194+
});

0 commit comments

Comments
 (0)