Skip to content
This repository has been archived by the owner on Jan 11, 2023. It is now read-only.

What's the best time to pushState? #674

Open
mrkishi opened this issue May 8, 2019 · 0 comments
Open

What's the best time to pushState? #674

mrkishi opened this issue May 8, 2019 · 0 comments
Labels

Comments

@mrkishi
Copy link
Member

mrkishi commented May 8, 2019

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?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants