-
-
Notifications
You must be signed in to change notification settings - Fork 419
What's the best time to pushState? #674
Description
Currently, after a link click or a call to goto(), Sapper will immediately history.pushState(). While this provides users an indication they're going somewhere (since we still don't have browsers' default loading indicators, major bummer), I don't know if this is a good behavior overall.
Normally, browsers won't change the URL until a page is somewhat loaded. I don't know at what point exactly they decide it's okay to commit the change (anyone?), but it's definitely not immediate. I'm sure this varies by browsers and versions, but on first glance both Chrome and Firefox only commit the navigation after receiving the first byte of the response body on my machine. This includes redirects, so temporary URLs are never committed.
To test this out, I whipped up a simple test server. Feel free to investigate on different environments:
Test server
const { exec } = require('child_process');
const { createServer } = require('http');
const wait = (ms = 5000) => new Promise(resolve => setTimeout(resolve, ms));
const server = createServer(async (request, response) => {
const report = (message) => console.log(message.padEnd(10), request.url);
report('in');
switch (request.url) {
case '/':
response.writeHead(200, { 'content-type': 'text/html' });
response.end(`
<br><a href="direct">direct</a>
<br><a href="redirect">redirect</a>
`);
break;
case '/direct':
await wait();
response.writeHead(200, { 'content-type': 'text/html' });
response.flushHeaders();
report('headers');
await wait();
response.write(`
first bytes
<script>
addEventListener('DOMContentLoaded', console.log)
addEventListener('load', console.log)
</script>
`);
report('bytes');
await wait();
response.end(`
<br><a href="/">back</a>
`);
break;
case '/redirect':
await wait();
response.writeHead(302, { location: '/direct' });
response.flushHeaders();
report('headers');
await wait();
response.end();
break;
default:
response.statusCode = 404;
response.end();
}
report('out');
});
server.listen(() => {
const start = process.platform === 'darwin' ? 'open'
: process.platform === 'win32' ? 'start'
: 'xdg-open';
const url = `http://localhost:${server.address().port}`;
console.log(`server listening on`, url);
exec(`${start} ${url}`);
});Sapper does use replaceState for redirects, but it's still clumsy if you have a slow network or the redirect fails. You can see all URLs in a redirect chain and, in fact, if you click a link and while it's loading you click some other link, the never-rendered one will be in your history and, worse, it'll have the original page's title.
I'm not sure on what the best approach for this would be, but I imagine at the very least we could postpone history manipulation at least until preloads settle. This would probably force apps to implement loading indicators if they don't have one, but until (if ever) we get APIs to trigger native browser loading indicators, this is probably a good idea anyway.
Other than that, we could even partly render changes before committing the history, but I have no informed opinion on that for now.
Thoughts?