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

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

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

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

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

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

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

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

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

Конечно 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 создан специально для того, чтобы понимать, был ли скрипт загружен из кэша.

Заключение

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

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

Комментарии (11)


  1. Fane28
    19.08.2024 13:21
    +2

    Получается что ни один из способов не гарантирует 100% достоверность информации. Досадно


  1. pae174
    19.08.2024 13:21
    +3

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

    Посмотреть в логи сервера?


    1. zhenkuzne Автор
      19.08.2024 13:21

      Как вариант, но если статика находится на CDN - то не подходит.


      1. pae174
        19.08.2024 13:21

        Поставить себе прокси сервер и смотреть его логи.


        1. zhenkuzne Автор
          19.08.2024 13:21

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


          1. pae174
            19.08.2024 13:21

            Прокси на вашей стороне (на стороне разработчика/тестировщика) никак не влияет на эффективность. Он работает либо прямо на машине разработчика, который занимается всем этим исследованмем в devconsole браузера, либо на каком-то внутриофисном серваке, и обслуживает только тех клиентов (разработчиков) которые в явном виде предпишут браузеру гонять трафик через прокси.


            1. zhenkuzne Автор
              19.08.2024 13:21

              Это тоже не подходит — нам нужно собирать стату с реальных пользователей. Локально я могу посмотреть в DevTools и без прокси.


  1. Fasterpast
    19.08.2024 13:21

    А если каждый раз запрашивать файл с новым именем, а на стороне сервера сделать rewrite, отбрасывающей ненужное? Где-то я что-то такое видел...


    1. zhenkuzne Автор
      19.08.2024 13:21

      Можешь рассказать подробнее про идею, пожалуйста?

      Такое ощущение, что если будем менять имя каждый раз, то вообще кэш отключим.


      1. Fasterpast
        19.08.2024 13:21
        +1

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


        1. zhenkuzne Автор
          19.08.2024 13:21

          Кажется, понял — контролируемо отключить кэш и собрать статистику. Спасибо, не думал в эту сторону.