Как понять, что сайт был загружен из кэша
- вторник, 20 августа 2024 г. в 00:00:07
Не так давно потребовалось узнать, что сайт был загружен из кэша — для просмотра и сравнения скорости “холодного” старта и скорости повторной загрузки, когда статические ресурсы уже закэшированы браузером.
Сначала казалось, что это простая задача, которую можно решить быстрым и надежным способом (и, скорее всего, есть готовые статьи на эту тему), но оказалось, что хороший способ найти не так просто, так же, информации на эту тему не особо много.
Эта статья показывает способы, с помощью которых можно узнать, был ли загружен ваш сайт из кэша или нет.
Какой критерий использовать для понимания того, что сайт был загружен из кэша?
Неоднозначный вопрос, решил остановиться на скорости загрузки точки входа в приложение — скрипта, который подключается на страницу и отвечает за старт приложения и загружает остальной код приложения.
Что будем загружать?
Конечно 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'
.
Способ, который является самым надежным — использование параметра 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
создан специально для того, чтобы понимать, был ли скрипт загружен из кэша.
Я остановился на четвертом способе — он выглядит самым надежным и относительно простым.
Какие способы знаете вы? Будет забавно, если есть решение без минусов перечисленных способов и я просто не смог до него добраться.