Can link rel=preload be made to work with fetch?
Asked Answered
M

5

24

I have a large JSON blob I would like to have preloaded with my webpage. To do this, I have added <link rel="preload" as="fetch" href="/blob.json"> to my page. I also have a JS request to fetch the same blob.

This does not work, and the console reports:

[Warning] The resource blob.json was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it wasn't preloaded for nothing.

MDN claims that this can be fixed by adding crossorigin to the link tag. AFAICT, this is not true, and no combination or crossorigin attributes will actually make it work.

Using the copy-as-curl command from the developer console, it seems like there is no combination of link tag plus attributes that will issue the same request as a fetch/XHR call in JS.

I would love to be wrong about this.

Marielamariele answered 3/10, 2018 at 21:2 Comment(0)
O
32

Here is the working solution to preload fetch that works both in Chrome and Safari and supports cookies.

Unfortunately, it works only for the same domain requests.

First, do not specify crossorigin attribute for preload tag, this will ensure Safari will send request in no-cors mode and include cookies

<link rel="preload" as="fetch" href="/data.json">

Second, the fetch api request should also be done in no-cors mode and include credentials (cookies). Pay attention that this request cannot have any custom headers (like Accept, Content-Type, etc), otherwise the browser won't be able to match this request with preloaded one.

fetch('/data.json', {
    method: 'GET',
    credentials: 'include',
    mode: 'no-cors',
})

I have tried other combinations of crossorigin attribute value and fetch API configuration, but none of them worked for me in Safari (only in Chrome).

Here is what I have tried:

<link rel="preload" as="fetch" href="/data.json" crossorigin="anonymous">

<script>
fetch('/data.json', {
    method: 'GET',
    credentials: 'same-origin',
    mode: 'cors',
})
</script>

The above works in Chrome, but not in Safari, because cookies are not sent by preload request in Safari.

<link rel="preload" as="fetch" href="/data.json" crossorigin="use-credentials">

<script>
fetch('/data.json', {
    method: 'GET',
    credentials: 'include',
    mode: 'cors',
})
</script>

The above works in Chrome but not in Safari. Though cookies are sent by preload request, Safari is not able to match fetch with preload request probably because of the different cors mode.

Obligee answered 9/9, 2020 at 15:35 Comment(1)
Why does this not have more upvotes!? I guess nobody else out there is prefetching JSON ¯_(ツ)_/¯Penult
F
4

Thanks to the discussion in this bug, I got fetch to work with preload in Chromium 67:

First, add crossorigin attribute to the preload link:

<link rel="preload" as="fetch" href="/blob.json" crossorigin="anonymous">

Second, add same-origin credentials to the fetch request:

fetch(url, {credentials: 'same-origin'}).then(response => {
    console.log(response);
});

Alternatively, you can use XMLHttpRequest instead of fetch (with XHR you don't need to add anything to the request), but only if you're not going to use responseType = 'blob' - it won't work due to another bug.

Forewent answered 7/1, 2020 at 11:59 Comment(1)
This approach doesn't work in Safari if /blob.json expects cookies. here i posted a working example with some explanations https://mcmap.net/q/548151/-can-link-rel-preload-be-made-to-work-with-fetchObligee
C
3

After fighting with this for some time, the most painless solution I've found is to do something similar to JSONP where you convert the json to javascript (exporting the value) and simply use <link rel="preload" as="script" .../> which doesn't have any cross browser issues.

That said, a couple observations to add to Eugene's answer above:

This case works in Safari for cross-origin requests (but not Chrome).

<link rel="preload" as="fetch" href="https://someotherorigin.com/data.json" crossorigin="anonymous">

<script>
fetch('https://someotherorigin.com/data.json', {
    method: 'GET',
    credentials: 'omit',
    mode: 'cors',
})
</script>

On investigating further, I came upon an interesting hack. It works with crossorigin and without special fetch settings. The technique is to preload as="image". This "works" (with a few caveats) on both Chrome / Safari.

<link rel="preload" as="image" href="https://someotherorigin.com/data.json" crossorigin="anonymous" />

<script>
setTimeout(()=>fetch('https://someotherorigin.com/data.json'),
500);
</script>

First caveat is that the preload must be finished for the fetch to use the cache. Otherwise the fetch makes a second request (this does not happen when preloading as="fetch"). Second is the link triggers an error event (which may show up in any monitoring software you are using). Third is you may still get a warning about an unused link preload (but you can see in the network tab the fetch is pulled from disk cache). You can avoid the warning by creating an offscreen <img> instead of the <link> but that's just piling a hack onto a hack. I suspect the json resource also requires headers allowing caching (but did not investigate this in depth).

Connaught answered 11/1 at 6:56 Comment(0)
M
1

It looks like this is a difference between Safari and Chrome. Safari posts the warning to console, but Chrome does not, so maybe adding crossorigin to the link element does solve the problem, but Safari has some kind of bug?

Marielamariele answered 4/10, 2018 at 14:3 Comment(4)
have you managed to get this to work? I am seeing Chrome making 3 requests instead of just the preload one - which what I am trying to achieve.Chafin
Yes. I only see one request for my JSON file on homicides.news.baltimoresun.com at the moment.Marielamariele
For future web spelunkers, the tag is <link rel="preload" as="fetch" href="/baltimore-homicides-victims-1543614381.json" id="victims-url" crossorigin> and the JS is just an XMLHttpRequest.Marielamariele
This works in Chrome, but still doesn't work in Safari 12.0.3; I see two requests for the preloaded JSON resource on homicides.news.baltimoresun.com and the console warning: "The resource homicides.news.baltimoresun.com/… was preloaded using link preload but not used within a few seconds from the window's load event. Please make sure it wasn't preloaded for nothing."Rutty
D
1

A key principle is to make the fetch request consistent as its preload request.

The preloading methods below are tested to work on Chrome, Firefox and Safari.

For same-origin preload,

<link rel="preload" as="fetch" href="./messages.json" />

<script>
fetch('./messages.json', {credentials: 'include', mode: 'no-cors'})
</script>

For cross-origin preload,

<link rel="preload" as="fetch" href="https://b.example.com/messages.json" crossorigin="anonymous" fetchpriority="low" />

<script>
fetch('https://b.example.com/messages.json', {mode: 'cors', priority: 'low'})
</script>

Notes:

  1. You need to configure server to enable CORS for "b.example.com".
  2. The fetchpriority attribute and the priority option are optional.

To check if a fetch used its preload, you may inspect the initiator types of the url requests with the Network tab of browser DevTools (for desktop Chromium, desktop Safari), or ‎Performance APIs (for Firefox or mobile Safari).

// wait for fetch requests to finish
let initiatorTypes = performance.getEntriesByType('resource').filter((e) => e.name.startsWith('https://b.example.com/messages.json')).map((e)=>e.initiatorType);
console.log(initiatorTypes);
Durfee answered 10/5 at 5:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.