Pull to refresh

Как понять, что сайт был загружен из кэша

Level of difficultyEasy
Reading time4 min
Views7.7K

Не так давно потребовалось узнать, что сайт был загружен из кэша — для просмотра и сравнения скорости “холодного” старта и скорости повторной загрузки, когда статические ресурсы уже закэшированы браузером.

Сначала казалось, что это простая задача, которую можно решить быстрым и надежным способом (и, скорее всего, есть готовые статьи на эту тему), но оказалось, что хороший способ найти не так просто, так же, информации на эту тему не особо много.

Эта статья показывает способы, с помощью которых можно узнать, был ли загружен ваш сайт из кэша или нет.

Репозиторий с кодом демо

Посмотреть работу демо

Варианты того, как можно узнать

Какой критерий использовать для понимания того, что сайт был загружен из кэша?

Неоднозначный вопрос, решил остановиться на скорости загрузки точки входа в приложение — скрипта, который подключается на страницу и отвечает за старт приложения и загружает остальной код приложения.

Что будем загружать?

Конечно jQuery, кэш между разными демо будем сбрасывать с помощью query-параметра.

Как отладить и проверить работу решения?

Можно отключать кэш с помощью галочки “Disable cache” в DevTools и смотреть на колонку "Size" чтобы понять, была загрузка или нет.

DevTools и проверка кэша
DevTools и проверка кэша

Способ первый — поставить флаг при загрузке страницы

Самый простой способ — если пользователь зашел на сайт, записать в долгосрочное хранилище браузера флаг, который можно будет извлечь при повторной загрузке и проверить, повторная это загрузка или нет. В качестве хранилища здесь и далее будет использоваться 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>

Это очень ненадежно — флаг в хранилище не говорит о том, что сайт был загружен из кэша. Например, после релиза флаг останется, а скрипты будут загружены новые. Так же, пользователь или браузер сам может почистить кэш, чтобы освободить место — в таком случае, флаг тоже может остаться.

Способ второй — вычислить скорость загрузки скрипта

Тоже просто — сохраняем время перед стартом загрузки скрипта, затем после и вычисляем разницу, если она оказалась мала — значит, взяли из кэша.

<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>

Это более надежно — если скрипта не будет в кэше, то время его загрузки будет значительно больше. Проблема только в том, что время загрузки из кэша может оказаться разным для разных устройств и браузеров, так же, если скрипт будет малого размера, то скорость загрузки может оказаться очень высокой.

Способ третий — загрузить скрипт вручную

Более изощренный метод — загружаем скрипт вручную, чтобы получить доступ к его заголовкам. Выбираем один из параметров, который будем сохранять в долгосрочном хранилище для сравнения в будущем. Параметр должен обновляться при каждой загрузке с сервера, например, 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>

Надежный способ, но смущает кастомная загрузка скриптов. Так же, Safari почему-то не берет из кэша без параметра cache: 'force-cache'.

Способ четвертый — использовать Perfomance API

Способ, который является самым надежным — использование параметра transferSize. Если ваши скрипты находятся на другом домене (так часто бывает при использовании CDN), ответ с кодом скрипта должен иметь заголовок Timing-Allow-Origin — иначе браузер будет всегда возвращать 0. Следующая проблема — Safari всегда отдает 0 при загрузке скриптов с другого домена, даже с заголовком. Для этого приходится делать фоллбэк на способ 2, если браузер не отдал значение 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>

Параметр transferSize создан специально для того, чтобы понимать, был ли скрипт загружен из кэша.

Заключение

Я остановился на четвертом способе — он выглядит самым надежным и относительно простым.

Какие способы знаете вы? Будет забавно, если есть решение без минусов перечисленных способов и я просто не смог до него добраться.

Tags:
Hubs:
Total votes 8: ↑6 and ↓2+9
Comments13

Articles