Кешування стилів. Основи клієнтського кешування зрозумілими словами і прикладах. Last-modified, Etag, Expires, Cache-control: max-age та інші заголовки. Цифровий відбиток URL

💖 Подобається?Поділися з друзями посиланням

Правильно налаштоване кешування дає величезний виграш у продуктивності, заощаджує трафік і зменшує витрати на сервер, проте на багатьох сайтах кешування реалізовано невдало, що створює стан гонки, що призводить до розсинхронізації взаємопов'язаних ресурсів.

Переважна більшість найкращих практик кешування відноситься до одного з двох патернів:

Паттерн №1: незмінний контент та довгий max-age кешу Cache-Control: max-age=31536000
  • Вміст URL не змінюється, отже…
  • Браузер або CDN можуть без проблем закешувати ресурс на рік
  • Закешований контент, який молодший за заданий max-age може використовуватися без консультації з сервером

Сторінка: Гей, мені потрібні "/script-v1.js", "/styles-v1.css" та "/cats-v1.jpg" 10:24

Кеш: У мене порожньо, як щодо тебе, Сервер? 10:24

Сервер: ОК, ось вони. До речі, Кеш, їх варто використати протягом року, не більше. 10:25

Кеш: Спс! 10:25

Сторінка: Ура! 10:25

Наступний день

Сторінка: Гей, мені потрібні "/script-v2.js", "/styles-v2.css" та "/cats-v1.jpg" 08:14

Кеш: Картинка з котиками є, решти немає. Сервер? 08:14

Сервер: Легко – ось нові CSS & JS. Ще раз, Кеш: їхній термін придатності не більше року. 08:15

Кеш: Супер! 08:15

Сторінка: Дякую! 08:15

Кеш: Хм, я не користувався "/script-v1.js" & "/styles-v1.css" досить довго. Настав час їх видаляти. 12:32

Використовуючи цей патерн, ви ніколи не змінюєте контент певного URL, ви змінюєте сам URL:

У кожному URL є щось, що змінюється одночасно із контентом. Це може бути номер версії, модифікована дата або хеш контенту (цей варіант я вибрав для свого блогу).

У більшості серверних фреймворків є інструменти, що дозволяють легко робити подібні речі (у Django я використовую Manifest Static Files Storage); є також зовсім невеликі бібліотеки в Node.js, які вирішують ті самі завдання, наприклад, gulp-rev.

Однак цей патерн не підходить для речей на кшталт статей та записів у блогах. Їх URL не можна версіонувати, а вміст може змінитися. Серйозно, у мене часто бувають граматичні та пунктуаційні помилки, тому потрібна можливість швидкого оновлення вмісту.

Паттерн №2: контент, що змінюється, завжди проходить ревалідацію на сервері Cache-Control: no-cache
  • Вміст URL зміниться, отже…
  • Будь-яка локальна закешована версія не може використовуватись без вказівки сервера.

Сторінка: Гей, мені потрібний вміст "/about/" та "/sw.js" 11:32

Кеш: Нічим не можу допомогти. Сервер? 11:32

Є такі. Кеш, тримай їх при собі, але перед використанням питай у мене. 11:33

Кеш: Так точно! 11:33

Сторінка: Спс! 11:33

На наступний день

Сторінка: Гей, мені знову потрібний вміст "/about/" та "/sw.js" 09:46

Кеш: Хвилинку. Сервере, з моїми копіями все гаразд? Копія /about/ лежить з понеділка, а /sw.js вчорашня. 09:46

Сервер: "/sw.js" не змінювалася… 09:47

Кеш: Круто. Сторінка, тримай "/sw.js" . 09:47

Сервер: …але "/about/" у мене нової версії. Кеш, тримай її, але як і минулого разу, не забудь спочатку спитати мене. 09:47

Кеш: Зрозумів! 09:47

Сторінка: Чудово! 09:47

Примітка: no-cache не означає "не кешувати", тобто "перевіряти" (або ревалідувати) закешований ресурс у сервера. А не кешувати зовсім браузеру наказує no-store. Також і must-revalidate означає не обов'язкову ревалідацію, а те, що закешований ресурс використовується тільки якщо він молодший, ніж заданий max-age , і тільки в іншому випадку він ревалідується. Ось так все запущено з ключовими словамидля кешування.

У цьому патерні ми можемо додати до відповіді ETag (ідентифікатор версії на ваш вибір) або заголовок Last-Modified. При наступному запиті вмісту з боку клієнта виводиться If-None-Match або If-Modified-Since відповідно, дозволяючи серверу сказати "Використовуй те, що у тебе є, твій кеш актуальний", тобто повернути HTTP 304.

Якщо надсилання ETag / Last-Modified неможливе, сервер завжди надсилає вміст повністю.

Цей патерн завжди вимагає звернень до мережі, тому він не такий гарний, як перший патерн, який може обходитися без мережних запитів.

Це не рідкість, коли у нас немає інфраструктури для першого патерну, але точно також можуть виникнути проблеми з мережними запитами в патерні 2. У результаті використовується проміжний варіант: короткий max-age і контент, що змінюється. Це поганий компроміс.

Використання max-age із змінним контентом це, як правило, неправильний вибір

І, на жаль, він поширений, як приклад можна навести Github pages.

Уявіть:

  • /article/
  • /styles.css
  • /script.js

Із серверним заголовком:

Cache-Control: must-revalidate, max-age=600

  • Вміст URL змінюється
  • Якщо в браузері є кешована версія свіжіша за 10 хвилин, вона використовується без консультації з сервером
  • Якщо такого кешу немає, використовується запит до мережі, по можливості з If-Modified-Since або If-None-Match

Сторінка: Гей, мені потрібні "/article/", "/script.js" та "/styles.css" 10:21

Кеш: У мене нічого нема, як у тебе, Сервер? 10:21

Сервер: Без проблем, ось вони. Але запам'ятай, Кеш: їх можна використовувати протягом найближчих десяти хвилин. 10:22

Кеш: Є! 10:22

Сторінка: Спс! 10:22

Сторінка: Гей, мені знову потрібні "/article/", "/script.js" та "/styles.css" 10:28

Кеш: Упс, я перепрошую, але я втратив "/styles.css", але все інше у мене є, тримай. Сервер, можеш підігнати мені "/styles.css"? 10:28

Сервер: Легко, він уже змінився з того часу, як ти минулого разу забирав його. Найближчі 10 хвилин можеш сміливо використовувати його. 10:29

Кеш: Без проблем. 10:29

Сторінка: Дякую! Але, здається, щось пішло не так! Все зламалося! Що взагалі відбувається? 10:29

Цей патерн має право на життя при тестуванні, але ламає все у реальному проекті та його дуже складно відстежувати. У прикладі вище, сервер оновив HTML, CSS та JS, але виведено сторінку зі старими HTML та JS з кешу, до яких додано оновлений CSS із сервера. Розбіжність версій все псує.

Часто при внесенні значних змін до HTML ми змінюємо і CSS, для правильного відображення нової структури, і JavaScript, щоб і він не відставав від контенту та стилів. Усі ці ресурси незалежні, але заголовки кешування що неспроможні висловити це. В результаті у користувачів може виявитися остання версіяодного/двох ресурсів та стара версія інших.

max-age задається щодо часу відповіді, тому якщо всі ресурси передаються як частина однієї адреси, їхній термін закінчиться одночасно, але й тут зберігається невеликий шанс розсинхронізації. Якщо у вас є сторінки, що не містять JavaScript або інші стилі, терміни придатності їх кешу будуть розсинхронізовані. І гірше того, браузер постійно витягує вміст із кешу, не знаючи, що HTML, CSS, & JS взаємозалежні, тому він з радістю може витягнути щось одне зі списку та забути про все інше. Враховуючи всі ці фактори разом, ви повинні зрозуміти, що ймовірність появи версій, що не збігаються, досить велика.

Для користувача результатом може бути зламана розкладка сторінки чи інші проблеми. Від невеликих глюків до непридатного контенту.

На щастя, користувачі мають запасний вихід.

Оновлення сторінки іноді рятує

Якщо сторінку завантажено шляхом оновлення, браузери завжди проводять серверну ревалідацію, ігноруючи max-age. Тому, якщо у користувача щось зламалося внаслідок max-age, просте оновлення сторінки може все виправити. Але, зрозуміло, після того, як ложки знайдуться, осад все одно залишиться і ставлення до вашого сайту буде іншим.

Сервіс-воркер може продовжити життя цих багів

Наприклад, у вас є такий сервіс-воркер:

Const version = "2"; self.addEventListener("install", event => ( event.waitUntil(caches.open(`static-$(version)`)) .then(cache => cache.addAll([ "/styles.css", "/script .js "]))); )); self.addEventListener("activate", event => ( // …delete old caches… )); self.addEventListener("fetch", event => ( event.respondWith(caches.match(event.request) .then(response => response || fetch(event.request))); ));

Цей сервіс-воркер:

  • кешує скрипт та стилі
  • використовує кеш при збігу, інакше звертається до мережі

Якщо ми змінюємо CSS/JS, ми також збільшуємо номер version, що ініціює оновлення. Однак, оскільки addAll звертається спочатку до кешу, ми можемо потрапити у стан гонки через max-age та невідповідні версії CSS & JS.

Після того, як вони закешовані, у нас будуть несумісні CSS & JS до наступного оновлення сервіс-воркера - і це якщо ми знову не потрапимо при оновленні стан гонки.

Ви можете пропустити кешування у сервіс-воркері:

Self.addEventListener("install", event => ( event.waitUntil(caches.open(`static-$(version)`) ).then(cache => cache.addAll([ new Request("/styles.css", (cache: "no-cache")), New Request ("/script.js", (cache: "no-cache"))))));));

На жаль, опції для кешування не підтримуються в Chrome/Opera і тільки-но додані в нічну збірку Firefox , але ви можете зробити це самостійно:

Self.addEventListener("install", event => ( event.waitUntil(caches.open(`static-$(version)`)) .then(cache => Promise.all([ "/styles.css", "/script .js" ].map(url => ( // cache-bust using a random query string return fetch(`$(url)?$(Math.random())`)).then(response => ( // fail on 404, 500 etc if (!response.ok) throw Error("Not ok");return cache.put(url, response);

У цьому прикладі я скидаю кеш за допомогою випадкового числа, але ви можете піти далі і додавати хеш контенту під час складання (це схоже на те, що робить sw-precache). Це свого роду реалізація першого патерну з допомогою JavaScript, але працює тільки з сервіс-воркером, а не браузерами та CDN.

Сервіс-воркери та HTTP-кеш чудово працюють разом, не змушуйте їх воювати!

Як бачите, ви можете обійти помилки з кешуванням у вашому сервіс-воркері, але правильніше вирішити корінь проблеми. Правильне налаштуваннякешування не тільки полегшує роботу сервіс-воркера, але й допомагає браузерам, які не підтримують сервіс-воркери (Safari, IE/Edge), а також дозволяє витягти максимум з вашої CDN.

Правильні заголовки кешування можуть значно спростити оновлення сервіс-воркера.

Const version = "23"; self.addEventListener("install", event => ( event.waitUntil(caches.open(`static-$(version)`)) .then(cache => cache.addAll([ "/", "/script-f93bca2c)). js", "/styles-a837cb1e.css", "/cats-0e9a2ef4.jpg" ]))); ));

Тут я закешував кореневу сторінку з патерном №2 (серверна ревалідація) та решту ресурсів з патерном №1 (незмінний контент). Кожне оновлення сервіс-воркеру буде викликати запит до кореневої сторінки, а решта ресурсів завантажуватиметься тільки, якщо їх URL змінився. Це добре тим, що зберігає трафік і покращує продуктивність, незалежно від того, чи ви оновлюєтеся з попередньою або дуже старої версії.

Тут є значна перевага над нативною реалізацією, коли цілий бінарник завантажується навіть при невеликій зміні або викликає комплексне порівняння двійкових файлів. Так ми можемо оновити велику веб-додаток за порівняно невеликого завантаження.

Сервіс-воркери працюють краще як поліпшення, а не тимчасового милиця, тому працюйте з кешем замість того, щоб воювати з ним.

При акуратному використанні max-age і контент, що змінюється, можуть бути дуже хороші

max-age дуже часто буває неправильним вибором для контенту, що змінюється, але не завжди. Наприклад, оригінал статті max-age складає три хвилини. Стан гонки не є проблемою, так як на сторінці немає залежностей, що використовують однаковий патерн кешування (CSS, JS & зображення використовують патерн №1 - незмінний контент), все інше цей патерн не використовує.

Цей патерн означає, що я спокійно пишу популярну статтю, а мій CDN (Cloudflare) може зняти навантаження із сервера, якщо, звичайно, я готовий почекати три хвилини, доки оновлена ​​стаття стане доступною для користувачів.

Цей патерн слід використовувати без фанатизму. Якщо я додав новий розділ у статтю і поставив на нього посилання з іншої статті, я створив залежність, яку треба дозволяти. Користувач може клацнути на посилання і отримати копію статті без розділу. Якщо я хочу уникнути цього, я маю оновити статтю, видалити кешований варіант статті з Cloudflare, почекати три хвилини і тільки після цього додавати посилання в іншу статтю. Так, цей патерн потребує обережності.

При правильному використанні кешування дає значне покращення продуктивності та економію трафіку. Передавайте незмінний контент, якщо ви можете легко змінити URL-адресу, або використовуйте серверну ревалідацію. Змішуйте max-age і змінний контент, якщо ви досить сміливі і впевнені, що ваш контент не має залежностей, які можуть розсинхронізуватися.

При зміні сайтів ми часто стикаємося з тим, що вміст сторінок, css-файлів і скриптів (.js) кешується браузером і залишається незмінним досить довгий час. Це призводить до того, що для того, щоб внесені зміни відобразились у всіх браузерах, потрібно привчати клієнтів до складних комбінацій F5 або Ctrl+F5. І час від часу стежити, щоб вони натискалися.

Процес досить нудний та незручний. Можна звичайно вийти із ситуації, перейменовуючи щоразу файли, але знову ж таки незручно.

Однак є спосіб, який дозволить залишитися при колишніх іменах, і скидати кешування.css або.js файлів у той момент, коли це буде потрібно нам. І назавжди забути про Ctrl+F5.

Суть полягає в тому, що ми приписуватимемо до наших.css або.js файлів в кінці псевдопараметр, який змінюватимемо час від часу, тим самим скидаючи кеш у браузері.

Таким чином, запис у вихідний кодтепер виглядатиме так:

Де 186485 - довільна комбінація, яка виведе той самий файл, але браузер інтерпретує його як новий завдяки псевдопараметру ?186485

Тепер, щоб щоразу не змінювати всі входження нашого параметра, поставимо його у php-файл, який підключимо до всіх потрібних нам місць:



Розповісти друзям