Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there any way to have the back button load just a cached page? #7936

Open
1 task done
martinmckenna opened this issue Nov 8, 2023 · 10 comments
Open
1 task done
Labels
bug:unverified feat:routing feat:scroll Issues related to scroll restoration

Comments

@martinmckenna
Copy link

martinmckenna commented Nov 8, 2023

What version of Remix are you using?

2.0.1

Are all your remix dependencies & dev-dependencies using the same version?

  • Yes

Steps to Reproduce

I was observing my own site and a few sites in the Who's using remix in production discussion and it seems like out-of-the-box, Remix has this issue with the back button on mobile browsers where the back button doesn't really perform like you'd expect it to. It seems like by default, the page's loader is always re-run and you end up with this really janky experience:

  1. Navigate to new page
  2. Swipe to go back (or hit back button)
  3. See flash of the latest page you were on, then the page you expect to see appears

Here's like 4 different examples I found pretty easily just by browsing through that discussion:

video of https://datagunung.com

https://imgur.com/aPe9TiN

video of https://hub.findkit.com

https://imgur.com/50NHxCH

video of https://basementcommunity.com

https://imgur.com/xruVJnD

video of https://ajourney.io

https://imgur.com/5vmVsaO

Now, from what I can tell, HTTP Cache control headers don't really solve this problem. I tried implementing them on both the document and each loader individually, and it seems like this issue still happens. I'm not sure if this is just a side-effect of using client-side hydration after the first render, or perhaps a side-effect of having markup that is generated at the time the page is requested, but I feel like getting a cached page when hitting the back button should be behavior default to Remix without the developer having to figure this out.

Expected Behavior

Page flashes old content before showing right content when hitting back button on mobile browsers

Actual Behavior

Page loads cached page with no flashing on mobile browsers

@martinmckenna
Copy link
Author

Also from what I can tell, this has been an issue with Remix from the start. I don't really have experience with Next.js, so I'm not sure if this is the norm with all universal-rendered frameworks that hydrate the client after first load, but I have a hunch that it is the norm.

Can this problem even be solved?

@brophdawg11
Copy link
Contributor

This looks like it might have something to do with <ScrollRestoration>. Looks like the new react docs site had this same issue a while back and had to "hack" in a fix for safari only :/

https://twitter.com/dan_abramov/status/1568219286744797190
reactjs/react.dev#5026
reactjs/react.dev#5029

Anyone want to take a stab at a fix for this and see if the same thing works for <ScrollRestoration> in Remix? The code itself is actually in react-router-dom so you could play around with direct edits to that in node_modules in your Remix app as a way to test it out locally.

https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/index.tsx#L1627

@brophdawg11 brophdawg11 added the feat:scroll Issues related to scroll restoration label Nov 8, 2023
@martinmckenna
Copy link
Author

martinmckenna commented Nov 8, 2023

huh interesting. i'll try to give this a shot on my site. FWIW though, a lot of those examples I hadn't scrolled yet

e: i can also replicate on iOS chrome as well

@mikkpokk
Copy link

mikkpokk commented Nov 9, 2023

From my experience, it's happening only when client side logic receives new information from loader and forces history DOM to re-render and re-calculate. This is not the case, when your route's view is persistent or it's height stays the same as it is on history. When your first render (SSR) page looks the same as it is after first CSR will be applied, you will not see the jumping effect.

This flashing and jumpy effect can be avoided. It just requires a bit to think through the archidecture and rendering logic. Popular thing is to render null or a loading spinner in component during the load time - you should avoid that wherever possible. Use better loading indicators if necessary (ex. overlay loader, loader bar on top of the page). Easiest way to understand what's going on your site, is to disable javascript temporary and navigate without javascript. Main reason, why this side-effect is so popular on production sites, is because developers are used to write CSR React.

Challenging topic is to avoid jumping effect on infinity pagination. There are loads of material about it on Google however and it isn't Remix issue, it's actually classical infinity pagination issue. That's why Facebook never destroy it's timeline from DOM when you navigate to another page.

About the ScrollRestoration of Remix - it's actually A+ grade component from my experience. About 1.5 year ago I used next.js on my projects and their ScrollRestoration was very chaotic and unpredictable back then. Not sure, if they have improved it by now.

Only case when revalidation of loaders during navigation on browser history stack causes the problem, is the case when entities sort order has changed (ie. new entity has been added or some entity has been resorted). It doesn't cause jumpy effect, it just replaces history elements with new sort order. I don't think that's expected UX behaviour. Users, who using browser navigation, usually expect to continue browsing where they left off.

So maybe this side-effect worths a discussion to add an option to opt-out from revalidation during the browser history navigation?

@hanayashiki
Copy link

Also from what I can tell, this has been an issue with Remix from the start. I don't really have experience with Next.js, so I'm not sure if this is the norm with all universal-rendered frameworks that hydrate the client after first load, but I have a hunch that it is the norm.

Can this problem even be solved?

On iOS safari, if you visit next.js's documentation site, which is built upon nextjs itself, try to scroll back, you will have similar flashing issue. I think this might be a case in which safari does not behave well. No problems on chrome.

@hanayashiki
Copy link

hanayashiki commented Nov 13, 2023

There in fact two problems during iOS swipe back action:

  1. It shows a grey background instead of a snapshot of last page.
  2. It does not go to the previous page at the same tick that popstate is fired. So the old page still shows and only after the loading is done, the previous page shows.

For problem 1, I'm not sure with the reason why a grey background shows. It seems this is the behavior if safari sees history.scrollRestoration === 'auto'? It should be a Safari problem.

For problem 2, for the current Remix which does not cache the data of useLoader, there can be hardly a perfect solution. Imagine that popstate fires at the end of your swiping gesture, and you are supposed to see the previous page right after, but Remix makes you await the loader data, how can you see the loading previous page? You need to wait for the previous page's data to load, so the old page will still show.

@martinmckenna
Copy link
Author

martinmckenna commented Nov 15, 2023

just so it's on the record (and i haven't attempted the hack/fix yet), i can absolutely reproduce this on mobile chrome as well

just want this made aware before we write this off as a safari-only bug:

trim.BB7CDD53-421C-4F02-89A7-48BC769396CF.MOV

i feel like there's 2 different issues here

  1. the flashing of the previous page's content
  2. the flashing of the blank white page

@mikkpokk
Copy link

@martinmckenna I can reproduce issue on datagunung.com, however I am not able to reproduce issue on remix.run, shopify.com and on my projects.

I digged deeper and I can confirm datagunung have standard issue with infinity pagination which causes re-renders and blinking during back/forward navigation. You can read more about that on comment above.

Delay during back/forward navigation is caused by revalidation, which is different issue and discussion. It requires storing each state of the page to local_storage or session_storage to offer option to disable revalidation during browser history navigation. Currently, Remix do not save those states and that's why revalidation is required.

@r0stiars0
Copy link

Hi @martinmckenna, I'm the maintainer of datagunung.com.
Thanks for highlighting the issue.

Have you got a chance to test this back button behavior in Android?

Here is one example that I was able to capture:
https://imgur.com/a/mLyeZrT

or
https://github.com/remix-run/remix/assets/101281822/d15acc5d-97cb-449c-8115-0dd56e44e8a3

@martinmckenna
Copy link
Author

@r0stiars0 just iOS safari and chrome so far. it's not a problem on non-mobile, so i'm not surprised that Android doesn't have this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug:unverified feat:routing feat:scroll Issues related to scroll restoration
Projects
None yet
Development

No branches or pull requests

5 participants