How to understand that the site was loaded from the cache

Not long ago, it was necessary to know that a site was loaded from cache – to view and compare the speed of a “cold” start and the speed of reloading, when static resources are already cached by the browser.

At first it seemed like a simple task that could be solved in a quick and reliable way (and most likely there are ready-made articles on this topic), but it turned out that a good way to find it is not so easy, and there is not much information on this topic.

This article shows you ways to find out whether your website was loaded from cache or not.

Repository with demo code

View demo in action

Options for how you can find out

What criteria should be used to understand that a site was loaded from cache?

An ambiguous question, I decided to focus on the loading speed of the entry point to the application – the script that connects to the page and is responsible for starting the application and loading the rest of the application code.

What are we going to download?

Of course jQuerywe will reset the cache between different demos using the query parameter.

How to debug and test the solution?

You can disable the cache using the “Disable cache” checkbox in DevTools and look at the “Size” column to see if there was a download or not.

DevTools and cache checking

DevTools and cache checking

Method one – set a flag when loading the page

The easiest way is to write a flag to the browser's long-term storage if the user has visited the site, which can be retrieved upon reloading and checked whether this is a reload or not. From here on, the storage will be Web Storage API.

<script id="entry-point" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js"></script>
<script>
  const entryPointSrc = document.getElementById('entry-point').src

  printResult(`From cache: ${entryPointSrc === localStorage.getItem('previousEntryPointSrc')}`)
  localStorage.setItem('previousEntryPointSrc', entryPointSrc)
</script>

This is very unreliable – the flag in the storage does not indicate that the site was loaded from the cache. For example, after the release, the flag will remain, and new scripts will be loaded. Also, the user or browser can clear the cache itself to free up space – in this case, the flag may also remain.

Method two – calculate the script loading speed

It’s also simple – we save the time before the script load starts, then after, and calculate the difference; if it turns out to be small, it means we took it from the cache.

<script>
  const timestampBeforeLoad = Date.now()
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js"></script>
<script>
  const timestampAfterLoad = Date.now()
  const loadingDuration = timestampAfterLoad - timestampBeforeLoad

  printResult(`From cache: ${loadingDuration < 20}`)
</script>

This is more reliable – if the script is not in the cache, then its loading time will be much longer. The only problem is that the loading time from the cache may be different for different devices and browsers, also if the script is small, then the loading speed may be very high.

Method three – download the script manually

A more sophisticated method is to manually load the script to access its headers. Select one of the parameters to save in long-term storage for future comparison. The parameter should be updated each time it is loaded from the server, for example, Expires.

<script id="entry-point" data-src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script>
  (async () => {
    const entryPointElement = document.getElementById('entry-point')
    /** Safari не берет из кэша без force-cache */
    const response = await fetch(entryPointElement.dataset.src, {cache: 'force-cache'})

    if (response.ok) {
      entryPointElement.src = URL.createObjectURL(await response.blob())
      const expiresHeader = response.headers.get('Expires')

      printResult(`From cache: ${expiresHeader === localStorage.getItem('previousExpiresHeader')}`)
      localStorage.setItem('previousExpiresHeader', expiresHeader)
    }
  })()
</script>

A reliable method, but the custom loading of scripts is confusing. Also, Safari for some reason does not take from the cache without parameter cache: 'force-cache'.

Method four – use the Performance API

The most reliable method is to use parameter transferSize. If your scripts are on a different domain (this is often the case when using a CDN), the response with the script code should have title Timing-Allow-Origin – otherwise the browser will always return 0. The next problem is that Safari always gives 0 when loading scripts from another domain, even with a header. To do this, you have to make a fallback to method 2 if the browser did not return the value transferSize.

<script id="entry-point" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
<script>
  const perfomanceEntry = performance.getEntriesByName(document.getElementById('entry-point').src)[0]

  if (perfomanceEntry) {
    const canRecieveTransferSize = Boolean(perfomanceEntry.encodedBodySize)

    printResult(`From cache: ${canRecieveTransferSize ? !perfomanceEntry.transferSize : perfomanceEntry.duration < 20}`)
  }
</script>

Parameter transferSize created specifically to understand whether the script was loaded from the cache.

Conclusion

I settled on the fourth method – it looks the most reliable and relatively simple.

What methods do you know? It would be funny if there is a solution without the disadvantages of the listed methods and I just couldn't get to it.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *