Open jakearchibald opened 3 years ago
This is a really hard one, because I think it would be confusing to default to loading as if we were back in '/' when the URL bar actually says '/article-1' but you're right that this is a place where developers have to add special handling (basically they have to redo in JS all the things that the page formerly did).
I don't think we should go the route of making formerly same-document history entries cross-document, that also feels bad.
To me this problem is more about how fast we can display HTML to the user on a back navigation. Right now, it's a waste because when you're on a page with a URL, but you've done a history.pushState
, you know that this is the HTML you'd want to represent as the page. But when a back navigation occurs, you're going to have to re-construct this entire page, because this HTML is NOT stored in any cache, unlike a fresh page load.
So if I were to go about fixing this problem, I would think about ways that the browser could snapshot the contents of the DOM when the URL bar changes in a same-document way, as if that DOM had been requested explicitly from the network. Then, even though this isn't a full BFCache reload (since the JS context is recreated), you would at LEAST not lose the DOM content, which I assume is cheaper to cache than full JS context.
Keep in mind too that developers always have to handle this case, because at any moment the user could reload. Interestingly, reloading does transform entries into cross-document, so e.g. if you had /
and /article-1
as entries and refreshed on /article-1
, /
is now in a different document from the one you just loaded. This has always been fairly confusing to me - and I wish that a reload had different behavior.
In general app history builds on the existing session history model, and I don't think we have any particular opportunities to change it. So I think the behavior here should be the same as it is for pushState. I don't know how we'd spec something else.
I wonder how obvious it is to developers that they need to cater for this. It seems possible that the resources at:
My understanding is that typically SPAs are done by either:
So I don't think it's very typical that /
is treated specially and /article-1
is not prepared to display an app. After all, what would happen if the user copied and pasted the /article-1
URL to their friends?
So I don't think it's very typical that / is treated specially and /article-1 is not prepared to display an app. After all, what would happen if the user copied and pasted the /article-1 URL to their friends?
Well, I do think you're over-estimating apps.
But further I think what's more frustrating than having to handle this specially is that to handle this, you have to either keep server-side rendering in sync with client-side rendering, which isn't trivial OR you have to do all client-side rendering, which has time-to-interaction implications. Vs. with multi-page applications, browsers do a really good job of making reloads and navigations cheap, with SPA, reloads and navigations can be very expensive if JS context is lost.
After all, what would happen if the user copied and pasted the
/article-1
URL to their friends?
That bit is fine. The server sends HTML. It's when that document also needs to handle /
, that's the bit which might be problematic.
As you say, if all URLs are rewriting to the same app JS, it's fine. It's possible that's what everyone does.
@tbondwilkinson
Interestingly, reloading does transform entries into cross-document, so e.g. if you had
/
and/article-1
as entries and refreshed on/article-1
,/
is now in a different document from the one you just loaded.
I don't think so, not in Chrome anyway. Where are you seeing this?
Yeah, I think my mental model of how pushState
was supposed to work is broken. I thought that, as long as the pushed URL gave you access to the core content addressed by the URL, then you're good.
Interestingly, if I Google for 'pushState demo', the first result with a working demo is https://css-tricks.com/using-the-html5-history-api/, and the demo makes the same incorrect assumption.
I have a vague worry that the current API encourages this thinking. Eg:
// The amazing lightbox library!
appHistory.addEventListener("navigate", event => {
if (!event.canRespond || event.hashChange) return;
const url = new URL(event.destination.url);
if (!/\.(jpe?g|gif|png|avif|webp)$/.test(url.pathname)) return;
displayImageInAModal(url);
});
This captures navigations to images and displays them in-page. The link is sharable (it'll just point to the image). The back button will appear to work. It'll even sometimes work across documents thanks to bfcache.
I guess we just have to document that the above is not okay.
I don't think so, not in Chrome anyway. Where are you seeing this?
For instance starting on google.com
, call pushState(undefined, undefined, '/maps')
Refresh the page, you'll end up at the google maps frontend, not google search homepage with a '/maps' path.
Or am I misunderstanding what you mean?
The image example is interesting - I assume open in new tab/window would still work as expected. But perhaps we'd only allow making cross-document navigations same-document if the other document is of the same "type", e.g a webpage and not a resource.
Interestingly, reloading does transform entries into cross-document, so e.g. if you had
/
and/article-1
as entries and refreshed on/article-1
,/
is now in a different document from the one you just loaded.
Maybe I'm reading this wrong. Here's a test:
history.pushState({}, '', '/article-1')
.When you said "/
is now in a different document from the one you just loaded", I thought you meant that 4 would result in a change of document.
3 results in a change of document, but it updates both history entries, so 4 traverses to the same document.
Your console should show a 404 error since that path doesn’t exist at example.com. But the browser does attempt the load I believe and makes the network request.
But I believe 4 would result in a change of document, had the network request in 3 not been a 404, unless I'm wrong in my terminology and document doesn't mean what I think it means.
I'm not seeing that behaviour in Chrome, Firefox, or Safari https://static-misc-2.glitch.me/push-state-test/
Ah, now I see what you mean. You're right! :) Another fun quirk.
It's consistent with navigating away then pressing back (unless bfcache), and hash-change navigations, so that's one thing at least!
Although it doesn’t seem like this is something that can be solved (where it actually needs solving, I mean?) by AppHistory alone, perhaps if AppHistory ends up being friendly to pairing with the proposed URLPattern and/or “Declarative routing” APIs, this would in turn make it more straightforward and attractive to share a single source of truth for route definitions between the client, service worker, and backend. If that pattern were easier to realize, it might help reduce the odds of folks accidentally introducing disagreements between server and client routing.
@domenic I'm happy for this to close unless you're interested in solving the image use-case in https://github.com/WICG/app-history/issues/99#issuecomment-824629248, perhaps in some 'opt-in' way that forces the new history entry to use its own document state. Fwiw, my PR will probably support that.
Otherwise, feels like we should stick with the current behaviour.
I think we should probably stick with the current behavior, but I'm happy to keep this open to track efforts about documentation, or thinking if there's some way to steer people away from such behavior, or the ideas that @bathos mentions.
I'm trying to refresh myself on this to see if we can add documentation but I unfortunately lost track of the problem. Given the lightbox image code in https://github.com/WICG/app-history/issues/99#issuecomment-824629248 what is the problem scenario? Is the problem that the navigate
handler isn't properly handling navigations to other URLs and closing the modal in that case, so e.g. the back button never closes the modal?
/cool-app
/cool-app/photo.webp
/cool-app/photo.webp
(not in a lightbox)/cool-app
, but nothing else happens, they're still looking at the image.One solution might be an option that, in step 3, creates a new history entry with the same document, but a different document state.
This means the reload in step 4 would only replace the document in the second history entry.
This came up in https://github.com/whatwg/html/issues/6207 and it might be worth 'fixing' in the new API.
/
./article-1
, which is handled within the current document by responding to the "navigate" event.Currently, the equivalent
pushState
browser behaviour is:/article-1
fetched and displayed./
(no fetch).I wonder how obvious it is to developers that they need to cater for this. It seems possible that the resources at:
/
is a full app, capable of transitions etc etc/article-1
is just the article. HTML & CSS.…meaning that
/article-1
is unprepared for an internal state change to/
.Other ways this could be handled:
/article-1
fetched and displayed./
fetched and displayed.This means entries that were previously using the same document are now using different documents, which is weird in its own way. Although, this is what should happen in a literal interpretation of the spec.
Or:
/
fetched and displayed, but with/article-1
state./
(no fetch).This means fetching something other than the URL that's in the URL bar, but it loads the same code that previously handled the transition from
/
to/article-1
.