Не так давно потребовалось узнать, что сайт был загружен из кэша — для просмотра и сравнения скорости “холодного” старта и скорости повторной загрузки, когда статические ресурсы уже закэшированы браузером.
Сначала казалось, что это простая задача, которую можно решить быстрым и надежным способом (и, скорее всего, есть готовые статьи на эту тему), но оказалось, что хороший способ найти не так просто, так же, информации на эту тему не особо много.
Эта статья показывает способы, с помощью которых можно узнать, был ли загружен ваш сайт из кэша или нет.
Варианты того, как можно узнать
Какой критерий использовать для понимания того, что сайт был загружен из кэша?
Неоднозначный вопрос, решил остановиться на скорости загрузки точки входа в приложение — скрипта, который подключается на страницу и отвечает за старт приложения и загружает остальной код приложения.
Что будем загружать?
Конечно jQuery, кэш между разными демо будем сбрасывать с помощью query-параметра.
Как отладить и проверить работу решения?
Можно отключать кэш с помощью галочки “Disable cache” в DevTools и смотреть на колонку "Size" чтобы понять, была загрузка или нет.
Способ первый — поставить флаг при загрузке страницы
Самый простой способ — если пользователь зашел на сайт, записать в долгосрочное хранилище браузера флаг, который можно будет извлечь при повторной загрузке и проверить, повторная это загрузка или нет. В качестве хранилища здесь и далее будет использоваться 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)
pae174
19.08.2024 13:21+3Варианты того, как можно узнать
Посмотреть в логи сервера?
zhenkuzne Автор
19.08.2024 13:21Как вариант, но если статика находится на CDN - то не подходит.
pae174
19.08.2024 13:21Поставить себе прокси сервер и смотреть его логи.
zhenkuzne Автор
19.08.2024 13:21Тогда не сможем использовать CDN эффективно, если за статикой будем ходить через наш сервер.
pae174
19.08.2024 13:21Прокси на вашей стороне (на стороне разработчика/тестировщика) никак не влияет на эффективность. Он работает либо прямо на машине разработчика, который занимается всем этим исследованмем в devconsole браузера, либо на каком-то внутриофисном серваке, и обслуживает только тех клиентов (разработчиков) которые в явном виде предпишут браузеру гонять трафик через прокси.
zhenkuzne Автор
19.08.2024 13:21Это тоже не подходит — нам нужно собирать стату с реальных пользователей. Локально я могу посмотреть в DevTools и без прокси.
Fasterpast
19.08.2024 13:21А если каждый раз запрашивать файл с новым именем, а на стороне сервера сделать rewrite, отбрасывающей ненужное? Где-то я что-то такое видел...
zhenkuzne Автор
19.08.2024 13:21Можешь рассказать подробнее про идею, пожалуйста?
Такое ощущение, что если будем менять имя каждый раз, то вообще кэш отключим.
Fasterpast
19.08.2024 13:21+1Ну да, мы получаем возможность по желанию отключать кэш, можно для пользователей без сессии, скажем делать всегда "холодную" загрузку и собирать статистику...
zhenkuzne Автор
19.08.2024 13:21Кажется, понял — контролируемо отключить кэш и собрать статистику. Спасибо, не думал в эту сторону.
Fane28
Получается что ни один из способов не гарантирует 100% достоверность информации. Досадно