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

Astro's prefetch doesn't work in non-Chromium-based browsers #10464

Closed
1 task
smastrom opened this issue Mar 16, 2024 · 10 comments · Fixed by #10999
Closed
1 task

Astro's prefetch doesn't work in non-Chromium-based browsers #10464

smastrom opened this issue Mar 16, 2024 · 10 comments · Fixed by #10999
Labels
- P4: important Violate documented behavior or significantly impacts performance (priority) feat: prefetch Related to the prefetch feature (scope)

Comments

@smastrom
Copy link

smastrom commented Mar 16, 2024

Astro Info

Astro                    v4.5.5
Node                     v21.6.1
System                   macOS (arm64)
Package Manager          pnpm
Output                   server
Adapter                  @astrojs/cloudflare
Integrations             none

If this issue only occurs in one browser, which browser is a problem?

Firefox, Safari

Describe the Bug

According to the docs, Astro prefetch should work on any browser whether or not View Transitions are enabled (as it is not mentioned otherwise).

I virtually tried any combination under my astro.config.mjs: prefetch: true, prefetch.prefetchAll: true, etc. And also to manually append the data-astro-prefetch attribute as explained in the docs and enable/disable ViewTransitions.

Under the "Network" tab of both Firefox 124 (latest) and Safari 17.4 (latest) there is no way I can see the incoming documents.

The only way to see them in that tab is to manually call prefetch() in a script tag using { with: 'fetch' }, however, those pages are then requested again to the server once I click on the correspondent links.

On Chrome and Edge (and I suppose any other Chromium-based browser) everything works as per docs.

I am not sure if this expected, if it's a bug or if I (hopefully) misinterpreted the docs and actually doing something wrong. Thank you.

What's the expected result?

To work on Firefox and Safari as it works on Chrome.

Link to Minimal Reproducible Example

https://stackblitz.com/edit/withastro-astro-rdn9rn?file=src%2Flayouts%2FLayout.astro

Participation

  • I am willing to submit a pull request for this issue.
@github-actions github-actions bot added the needs triage Issue needs to be triaged label Mar 16, 2024
@bluwy
Copy link
Member

bluwy commented Mar 18, 2024

I used Safari exclusively when implementing this, so it should work. Are you testing this in dev or prod? The prefetch behaviour also relies on a proper Cache-Control header set, so depending on your deployment platform, they may set a short Cache-Control so that it's cached on the second request. In dev, Cache-Control is usually not set so you might not see the benefits.

IIRC Chrome implements some unique heuristics, e.g. checking date of request and guessing a proper cache time if Cache-Control is not set. So it can sometime work for Chrome. https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#heuristic_caching

If you're unsure if the prefetching is actually done by the script, you can enable debug/verbose logging in the browser console to see the prefetch logs (only in dev).

@Princesseuh Princesseuh added needs response Issue needs response from OP and removed needs triage Issue needs to be triaged labels Mar 18, 2024
@smastrom
Copy link
Author

smastrom commented Mar 18, 2024

Hi @bluwy and thanks for your reply.

So, I've copied the StackBlitz repo linked above to GitHub and deployed it to Cloudflare. The result doesn't change. Instead of Chrome I am using Edge but the behavior on Chrome is the same. Sorry for the UI in Italian but it seems I cannot change it easily.

Edge / Chrome

If I refresh the about page, you can see contacts and index fetched in the network tab:

Screenshot 2024-03-18 alle 11 47 25

Then by navigating to contacts it's clear that it navigates to a prefetched page:

Screenshot 2024-03-18 alle 11 43 05

Safari

If I refresh the about page that's what I see in the network tab (no prefetched pages):

Screenshot 2024-03-18 alle 11 48 55

If I navigate to contacts, it's clear that that page has been downloaded as it took 216ms ("scarica" means "downloaded") and doesn't come from the prefetch cache:

Screenshot 2024-03-18 alle 11 51 42

On Firefox I have the same identical behavior of Safari.

I have published 4 Astro websites in the last few months and they are all affetcted by the same prefetch issue hence I guess it's not just my device or the dev environment.

Thank you again for your reply.

@bluwy
Copy link
Member

bluwy commented Mar 18, 2024

Thanks! That's very helpful to understand the current situation. It looks like:

  • For Safari, they don't support <link rel="prefetch" at all :( We do use a normal fetch() in some cases and I wonder if we can fallback to that for Safari.
  • For Firefox, it seems like it does prefetch, but it returns NS_BINDING_ABORTED for me. Searching online, it seems like because the headers didn't return Expires or Cache-Control so it decided to not prefetch it. It started streaming the HTML, but immediately abort it after the first chunk.
    image

I'm not exactly sure how to make these bullet-proof yet, but this is interesting to think about.

@smastrom
Copy link
Author

Great, thanks for providing additional insights! If I may add another couple of things I noticed:

Calling prefetch, { with: 'fetch'} in a script tag is the only way to trigger the fetch on Firefox and Safari:

<script>
   import { prefetch } from 'astro:prefetch'

   document.addEventListener('astro:page-load', () => {
      prefetch('/', { with: 'fetch' })
      prefetch('/about', { with: 'fetch' })
      prefetch('/contact', { with: 'fetch' })
   })
</script>

I can then see the pages downloaded in the network tab on both browsers:

Screenshot 2024-03-18 alle 18 21 24

But once I try to navigate to one of them, they're downloaded again:

Screenshot 2024-03-18 alle 18 21 38

I've added a branch to the repo linked above and deployed it Cloudflare just in case.

Cheers!

@matthewp matthewp added the feat: prefetch Related to the prefetch feature (scope) label Mar 23, 2024
@matthewp
Copy link
Contributor

@bluwy is this a bug?

@bluwy
Copy link
Member

bluwy commented Mar 24, 2024

Yeah, it should work for Safari and Firefox. I think we need to figure out a support path for it, otherwise we need to document the caveats.

@matthewp matthewp added - P4: important Violate documented behavior or significantly impacts performance (priority) and removed needs response Issue needs response from OP labels Apr 8, 2024
@martrapp
Copy link
Member

See also #10907

@V3RON
Copy link
Contributor

V3RON commented May 10, 2024

@bluwy

For Safari, they don't support <link rel="prefetch" at all :( We do use a normal fetch() in some cases and I wonder if we can fallback to that for Safari.

Sure we can!

document.createElement('link').relList.supports('prefetch') returns true in Chromium and false in Safari.

I'll investigate it further and probably open a PR if we agree on this solution.

@bluwy
Copy link
Member

bluwy commented May 10, 2024

@V3RON I was just working on this today and I noticed that 😄 I'll be sharing my findings here shortly and open a PR to restructure things, so a PR won't be needed for now, but thanks!

@bluwy
Copy link
Member

bluwy commented May 10, 2024

I ran some tests locally to see how this all works across different browsers, and how <link rel="prefetch"> or fetch() works among them, and how the existence of cache headers affect these:

Chrome

  • It supports <link rel="prefetch"> and will generally always work as expected.
  • <link rel="prefetch"> will continue to work regardless if any cache headers exist.

Firefox

  • It supports <link rel="prefetch"> but only if the resource has proper cache headers.
  • It will show a NS_BINDING_ABORTED error if Cache-Control or Expires don't exist, but if the response includes an ETag header (which is another way to cache), Firefox will still cache it and reuse on next navigation, so you could ignore the error.
  • If the response has no cache headers, prefetch will not work.

Safari

  • It doesn't support <link rel="prefetch">, and will fallback to fetch()
  • The problem with fetch(), which applies to all browsers, is that it needs a cache header in order to work. If there's none, the fetch() will be wasted.
  • Edge case: Safari private window will ignore the cache even if there's proper cache headers for some reason (at least in my tests, for ETag headers)

Notes about cache headers

  • In general, if you use SSG or prerender pages. The server will automatically apply ETag headers to them. Meaning you don't need to set Cache-Control or Expires.
  • The issue is more prominent for SSR-ed pages because the server can't apply or assume any cache headers by default. You need to add them yourself.

Summary

To re-iterate, if all pages have cache headers (Cache-Control, Expires, ETag, etc), it will work in all browsers. If a page has no cache header, it will not work with fetch (Safari) and it will not work with strict <link rel="prefetch"> (Firefox).

Next steps

  • I'll update prefetch to always prefer <link rel="prefetch"> where available, falling back to fetch() only if it's not supported.
  • The programmatic prefetch() API will deprecate the with option, it's better to guide users to using <link rel="prefetch"> where available.
  • I'll update the docs with the above information and advise on setting cache headers for SSR if they use prefetch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
- P4: important Violate documented behavior or significantly impacts performance (priority) feat: prefetch Related to the prefetch feature (scope)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants