Кешування
Зміст
«Я бачу стару версію» — речення, після якого команди витрачають години не туди. не знаходить нову кнопку, тест зелений на вчорашньому бандлі, у колеги все свіже, а у вас ні. Причина зазвичай не в застосунку: між браузером і сервером стоїть кілька сховищ, і кожне має власного господаря й власні правила.
Ця глава — повний виклад теми на сайті, інші глави посилаються сюди по механіку кешу. Мета — одне вміння: за хвилину сказати, з якого рівня прийшла відповідь і чому.
Хто кешує і на якій підставі
(HTTP caching) — це зберігання вже отриманих відповідей у проміжному сховищі, щоб наступного разу не тягнути їх мережею повторно. Під правила підпадає не лише статика: відповідь API — така сама HTTP-відповідь (їхні формати розбирає глава «REST API та формати даних»).
Головне для тестувальника: браузер сам не вирішує, що і як довго зберігати — правила диктує сервер заголовками відповіді. Тому поведінка кешу не «оптимізація десь усередині», а частина контракту між бекендом і фронтендом: її перевіряють на заголовок.
| Клас кешу | Кого обслуговує | Де живе |
|---|---|---|
| Приватний (private) | одного користувача | зазвичай усередині клієнта — браузера |
| Спільний (shared) | кількох користувачів | зазвичай у проміжному вузлі — проксі, CDN |
Спільні кеші додатково діляться на -кеші й керовані — reverse proxy, CDN і Service Worker разом із Cache API розгортають самі розробники сервісу. І контрінтуїтивне наостанок: навіть без Cache-Control відповідь за певних умов може бути збережена й перевикористана — це евристичне кешування.
Життєвий цикл запиту: у якій точці кеш перехоплює
Повний шлях запиту розбирає глава «Життєвий цикл запиту»: DNS, встановлення зʼєднання, надсилання, очікування сервера, відповідь, рендер. Кеш вклинюється в нього у трьох точках, і кожна здатна обірвати маршрут раніше.
Service Worker стоїть найближче до сторінки — за означенням це проксі між вебзастосунком, браузером і мережею. Далі приватний HTTP-кеш браузера: якщо копія придатна, мережею не йде нічого. Ще далі — спільні кеші в проміжних вузлах. Тому діагностика починається з визначення рівня, а не з коду застосунку.
Заголовки, якими сервер керує кешем
Основний важіль — Cache-Control із директивами через кому, наприклад Cache-Control: public, max-age=3600.
| Директива | Що означає |
|---|---|
max-age=N | скільки секунд відповідь свіжа |
s-maxage=N | те саме, але лише для спільного кешу |
no-cache | зберігати можна, перевикористати без валідації — ні |
no-store | не зберігати жодної частини запиту чи відповіді |
must-revalidate | застарілу відповідь без валідації не віддавати |
private | спільний кеш зберігати не повинен, приватний може |
public | знімає заборону зберігати — для будь-якого кешу |
Термін свіжості береться за першим збігом: s-maxage (лише для спільного кешу), далі max-age, далі Expires мінус Date. Звідси відповідь на класичне питання: коли є і max-age, і Expires, перемагає відносний max-age. Якщо директиви конфліктують — кеш виконує найсуворішу.
no-cache не означає «не кешувати» — це головна плутанина теми. Копія лягає в кеш, просто перед кожним використанням її валідують на сервері: трафік економиться (тіло не передається, якщо нічого не змінилось), а мережевий похід відбувається щоразу. no-store — інша річ: зберігати заборонено взагалі, і не економиться нічого. Окремо про безпеку: no-store не є механізмом приватності, і RFC каже це прямо — «This directive is not a reliable or sufficient mechanism for ensuring privacy».
Формула «no-cache — це те саме, що max-age=0, must-revalidate» правдива лише двоскладово. За MDN еквівалентність є: «It is often stated that the combination of max-age=0 and must-revalidate has the same meaning as no-cache». Там же сказано, що таке вживання max-age=0 — спадок реалізацій до HTTP/1.1, які не вміли обробляти no-cache, і що «there's no reason to ever use» цю комбінацію тепер. RFC 9111 директиви не ототожнює: тотожність — за MDN, не за специфікацією.
Свіжість проти валідації: умовний запит і 304
Відповідь у кеші живе у двох станах. Поки її вік не перевищив термін свіжості, вона свіжа (fresh) — її віддають без походу в мережу. Після цього вона застаріла (stale), і перед використанням кеш має перепитати сервер. Ця перевірка — валідація, або ревалідація.
Механізм — умовний запит: кеш надсилає валідатор, тобто значення, що описує версію ресурсу. ETag їде в If-None-Match, Last-Modified — в If-Modified-Since. ETag непрозорий: він ідентифікує версію, і розбирати його на частини не можна. Якщо версія збіглася, сервер відповідає 304 Not Modified — збережену відповідь можна оновити метаданими й перевикористати, тіло повторно не передається.
У мережевій панелі цей обмін виглядає так. Сервер раніше віддав ресурс із ETag: "33a64df5c1e9", свіжість минула — і кеш повертається з тим самим значенням:
GET /app.js HTTP/1.1
Host: example.com
If-None-Match: "33a64df5c1e9"
Версія та сама, тож відповідь приходить без тіла:
HTTP/1.1 304 Not Modified
ETag: "33a64df5c1e9"
Cache-Control: max-age=3600
Одна межа: ревалідація не гарантована для навігації по історії — наприклад, кнопкою «Назад»; при використанні bfcache (back/forward cache) браузер відновлює знімок сторінки без ревалідації. Тому сценарій «повернувся назад — маю побачити свіжі дані» перевіряють окремо: з no-cache він не випливає.
Спільний кеш проти приватного: private, public, s-maxage
Тут ховається найдорожча помилка теми — не , а інцидент безпеки: персоналізована відповідь, що потрапила у спільний кеш, може поїхати іншому користувачу.
Розмежовує кеші саме private: спільний кеш таку відповідь зберігати MUST NOT, приватний — MAY. Є й кваліфікована форма з переліком імен полів: тоді спільному кешу заборонено зберігати лише названі заголовки, а решту повідомлення — можна.
А public — це не «дозвіл спільним кешам», хоч так її переказують постійно: «The public response directive indicates that a cache MAY store the response even if it would otherwise be prohibited… In other words, public explicitly marks the response as cacheable». Директива знімає заборону для будь-якого кешу, а спільний кеш із заголовком Authorization — приклад у джерелі, а не зміст директиви. s-maxage задає окремий термін свіжості саме для спільних кешів: так тримають різні строки для CDN і для браузера.
Два заголовки для дебагу. Age — оцінка того, скільки секунд минуло від моменту, коли відповідь згенерували або ревалідували на origin-сервері, а не «скільки вона пролежала у спільному кеші». Vary додає названі заголовки запиту до ключа кешу, тож надто широкий Vary (класика — Vary: User-Agent) роздуває кеш.
Наша практика (не канон). Провайдерські заголовки на кшталт X-Cache: HIT чи CF-Cache-Status канонічного джерела не мають: назви й формат залежать від постачальника, тож звіряйтеся з його документацією.
CDN як спільний кеш і що це змінює для тестування
CDN (Content Delivery Network) — мережа проміжних серверів ближче до користувачів; вона кешує відповіді й знижує навантаження на origin-сервер, бо частина запитів на статику до нього просто не доходить. З погляду HTTP це звичайний спільний кеш. Для тестування зʼявляється рівень, на який ваш код не має впливу: кеш браузера можна вимкнути, — зняти, а спільний кеш живе на чужій машині в чужому регіоні.
Суперечність «кешувати довго, але бачити нове одразу» розвʼязує cache busting через зміну URL: змінився вміст — змінилася адреса. Практично це фінгерпринтинг, хеш вмісту в імені файлу (app.a1b2c3d4.js). Нова версія стає новим URL, тож старий кеш перестає бути релевантним і інвалідувати його не треба. Ключ схеми — HTML: його віддають із no-cache, бо саме він приносить актуальні імена, а хешовані асети — з річним immutable. Побічна деталь: хеші в іменах файлів — благо, а хеші в іменах CSS-класів — ні; локатор на автогенерованому класі ламатиметься на кожному .
Наша практика (не канон). Канону під це немає — далі досвід команд. Проміжні вузли CDN звуть edge-вузлами. Головна операційна проблема — інвалідація: віддали файл із s-maxage=86400, задеплоїли фікс, і вузли ще добу віддають стару версію; звідси механізми примусового скидання (purge). Діагностичне правило: тест на стенді стабільно бачить старий фронтенд, а локально свіжо — підозрюйте CDN, не свій код. Лікується це процесом (purge після , перевірка версії застосунку перед стартом — імʼя хешованого бандла в HTML тут готовий маркер), а не паузами: пауза перетворює проблему на плаваючий . Версія в query-рядку (app.js?v=20260708) менш надійна за хеш в імені файлу: деякі CDN ігнорують query-рядок у ключі кешу.
Посередники: проксі, шлюз, тунель
CDN — не єдине місце, де відповідь застрягає. HTTP описує три форми (intermediary), і той самий вузол може перемикатися між ролями залежно від запиту.
- Проксі (proxy) — агент пересилання, якого обирає клієнт; це і є визначальна ознака, а не «стоїть з боку клієнта». Типове застосування — звести запити організації через спільного посередника заради безпекових сервісів або спільного кешу.
- (gateway, він же reverse proxy) — посередник, який поводиться як origin-сервер для вихідного зʼєднання; його вживають, зокрема, щоб прискорити сервер кешуванням-прискорювачем і розділити навантаження між машинами.
- (tunnel) — сліпий ретранслятор, який повідомлень не змінює; активний тунель не вважається учасником HTTP-обміну.
Окремий клас — вузли, що фільтрують трафік без відома відправника. Такий interception proxy відрізняється від звичайного проксі тим, що його не обирає клієнт; протокольно він «indistinguishable (at a protocol level) from an on-path attacker». Висновок для тестів: у корпоративній мережі чи на публічному Wi-Fi поведінка запиту може змінитися без жодної зміни в застосунку.
Бонус для звітів: 502 і 504 віддає сервер, що діє «as a gateway or proxy» — зламалася ланка за посередником; 503 означає, що сам сервер тимчасово не здатен обробити запит.
Service Worker: кеш, який живе в коді сторінки
Service Worker — це JavaScript, який браузер виконує у фоні, окремо від сторінки, у власному потоці без доступу до DOM. Він працює як проксі між вебзастосунком, браузером і мережею, перехоплює навігаційні та ресурсні запити й доступний лише в захищеному контексті (HTTPS, з винятком http://localhost).
Ключове для тестів: у воркера власне сховище — Cache API, не повʼязане з HTTP-кешем браузера, доступне не лише воркеру («The Cache API is available from pages and service workers») і спільне на весь origin. Відповідь із нього віддається через FetchEvent.respondWith() — і лише там, де це прописано в обробнику: «the service worker only uses the cache when you tell it when and how».
Підступність додає життєвий цикл — Download, Install, Activate; встановлення запускається, лише коли файл трапився вперше або побайтово відрізняється від наявного; на install наповнюють кеш, на activate прибирають старі. Якщо активний воркер уже є, нова версія встановлюється у фоні, але не активується — це стан «worker in waiting»: контроль вона бере лише після закриття всіх вкладок сайту, якщо код не викликав skipWaiting(). Тому після деплою тест може працювати зі старою логікою кешування.
| Патерн джерела | Жаргон | Ціна |
|---|---|---|
| Cache, falling back to network | cache-first | ресурс із кешу мережу не питає — звідси «залипання» після деплою |
| Network falling back to cache | network-first | на повільному звʼязку користувач чекає, поки мережа впаде |
| Stale-while-revalidate | — | віддає старе зараз, оновлення підтягує на наступний раз |
Наша практика (не канон). Прямого твердження про взаємодію no-store і Cache API джерела не дають — це наш вивід із того, що сховища окремі: коректний no-store на бекенді воркера не стримає. Звідси й порядок підозр: якщо тест бачить примару старої версії, а HTTP-заголовки правильні, перша підозра — Service Worker.
Що видно у вкладці Network: позначки походження відповіді
Рівень, з якого прийшла відповідь, видно в мережевій панелі інструментів розробника (DevTools). Колонка Size показує заголовки плюс тіло, як їх віддав сервер, — але для відповідей із кешу вона показує не байти, а .
(from memory cache) чи (from disk cache) означає, що мережею не передавалося нічого: свіжість не минула, сервера навіть не питали. 304 Not Modified — навпаки, живий запит, у якому сервер підтвердив копію й не передав тіло. Два різні діагнози: у першому випадку зміна на сервері до вас не доїде, доки не мине свіжість, у другому ревалідація працює.
Поруч живе Disable cache, що відтворює досвід відвідувача, який заходить уперше: «This more accurately emulates a first-time user's experience». Межу його дії дока обумовлює панеллю, а не станом вікна: щоб вимкнути кеш під час роботи в інших панелях, беруть панель Network conditions. Детальний розбір панелі — глава «DevTools: вкладка Network і дебаг».
Наша практика (не канон). Самі позначки (from memory cache) і (from disk cache) у довіднику DevTools не описані — їхнє прочитання ми даємо з практики.
Перехоплення мережі як спосіб обійти кеш у тесті
Найнадійніший спосіб зробити тест незалежним від кешу — не вгадувати, що лежить у сховищах, а вирішувати за них. (network interception) — тема глави «Перехоплення й мокання мережі»; тут беремо з неї рівно те, що стосується кешу. Механізм дає тесту API для спостереження й зміни трафіку: під контроль потрапляють усі запити сторінки, зокрема XHR і fetch.
Обробник маршруту ухвалює одне з трьох рішень: пропустити (запит іде на сервер, дорогою можна змінити URL, метод, заголовки чи тіло), підмінити (сервер не викликається, браузер отримує рукотворну відповідь) або обірвати. Перехоплення вибіркове — запити поза шаблоном ідуть як завжди; маршрут на рівні контексту діє й на попапи.
// відповідь приходить із тесту, а не з кешу й не з мережі
await context.route('**/api/config', (route) =>
route.fulfill({ status: 200, body: JSON.stringify({ feature: 'on' }) }),
);
Дві пастки, які дока називає прямо. Заборонені заголовки (Cookie, Host, Content-Length та інші) підмінити не вийде — «If an override is provided for a forbidden header, it will be ignored»: тест, який «підмінив» Cookie, зеленіє на непідміненому запиті. І коли на один шаблон зареєстровано кілька обробників, спрацьовують вони у зворотному до реєстрації порядку, бо «the last registered route can always override all the previous ones»; передати керування далі можна через route.fallback(), тоді як route.continue() ланцюг зупиняє. Альтернатива ручній підміні — режим запису-відтворення через HAR: дані реалістичні, а від сервера тест уже не залежить.
Наша практика (не канон). Порядок «спершу маршрут, потім дія» джерело показує в прикладі, але як припис не формулює — формулюємо ми: правило діє лише на запити після реєстрації. Наш і другий висновок: залишений маршрут перехоплюватиме запити наступних тестів у тому самому воркері, тож його — частина ізоляції.
Практичний наслідок: свіжий контекст на прогін
Найдешевша ізоляція від кешу — свіжий профіль браузера на прогін; у Playwright роль профілю грає окремий browser context. Це прибирає накопичений стан без спеціального коду.
Service Worker знімають окремо, бо він живе поза HTTP-кешем. Нюанс, який рятує години: специфікація застерігає, що unregister() «only affects subsequent navigations» — у тому самому контексті воркер після виклику ще працює. Радикальніший варіант — опція контексту, яка не дає воркерам реєструватися:
const context = await browser.newContext({ serviceWorkers: 'block' });
І застереження, без якого попереднє стає карго-культом: вимикати кеш у всіх тестах підряд не можна. Якщо предмет перевірки — сама поведінка кешування (що на умовний запит справді повертається 304, що офлайн працює), кеш має бути ввімкнений: інакше ви тестуєте не той застосунок, який поїде в прод.
Наша практика (не канон). Далі — наш висновок із того, що рівнів кешу кілька; стандарту під це немає. «Стара версія» приходить із чотирьох місць: HTTP-кеш браузера, Cache API воркера, CDN або проміжний проксі та кеш усередині застосунку (стан у памʼяті, сховище браузера, кеш HTTP-клієнта). Саме тому «почистив кеш» не є відтворюваною процедурою: фраза не називає ні рівня, ні інструмента, ні моменту, а два місця з чотирьох ручному очищенню в браузері не піддаються — воркер знімається окремо і не одразу, а спільний кеш живе на чужій машині. Відтворювана процедура звучить інакше: свіжий контекст на прогін, воркер заблоковано, перехоплення там, де потрібен детермінізм, версія застосунку перевірена перед стартом.
Типові помилки
- Виглядає як «
no-cacheвимикає кеш», а насправді копія зберігається — її просто валідують перед кожним використанням. Зберігання вимикаєno-store, ціною повного тіла щоразу. - Виглядає як «поставили
no-store— дані захищені», а насправді RFC називає директиву ненадійним і недостатнім механізмом приватності. - Виглядає як «
publicдозволяє кешувати спільним кешам», а насправді вона знімає заборону для будь-якого кешу; спільними керуєprivate. - Виглядає як баг застосунку («кнопки немає»), а насправді завантажився старий бандл:
(from disk cache)і304— два різні діагнози. - Виглядає як «заголовки правильні, отже кеш не винен», а насправді Service Worker віддає копію з Cache API, на яку HTTP-заголовки не впливають.
- Виглядає як «
Ageпоказує, скільки відповідь лежить у CDN», а насправді це час від її генерації або ревалідації на origin-сервері.
Підсумок
- Правила кешу диктує сервер заголовками відповіді — тому кеш є частиною контракту, яку перевіряють асертами.
- Свіжа копія віддається без мережі; застаріла проходить умовний запит із валідатором, і
304означає «бери своє, тіло не шлю». - Кешів кілька й вони різні за господарем: приватний браузерний, спільний у посереднику та CDN, програмований Cache API воркера — тому діагностика починається з рівня.
privateрозмежовує спільний і приватний кеш,publicлише знімає заборону зберігати,no-storeне є механізмом приватності.- Ізоляція від кешу — свіжий контекст плюс явне поводження з воркером, а не ритуал «почистив кеш»; але там, де предметом тесту є сам кеш, вимикати його не можна.
Можливі питання
- «Чим
no-cacheвідрізняється відno-store?» Перевіряють, чи ви не читаєте директиву за назвою: сильна відповідь називає різницю у зберіганні й додає ціну кожного варіанта. - «Що таке
304і хто його ініціює?» Дивляться, чи розумієте, що це відповідь на умовний запит, а не помилка, і що перевірку ініціює кеш, надсилаючи валідатор. - «Що змінює CDN у тестуванні?» Питання про межу відповідальності. Очікують: CDN — спільний кеш, він слухає
privateіs-maxage, і це рівень, який тестовий код не може ні почистити, ні вимкнути, — його обходять перехопленням або процесом. - «Тест бачить стару версію. Ваші дії?» Міні-кейс, у якому слухають порядок: спершу рівень (що каже панель — кеш,
304чи живий запит), потім воркер, потім CDN, і лише тоді код. - «Як ізолювати тести від кешу?» Перевіряють, чи не почнете з паузи. Очікують свіжий контекст, свідоме поводження з воркером, перехоплення там, де потрібен детермінізм, — і застереження, що тести самого кешування вимикати кеш не можуть.
Джерела
- RFC 9111 — HTTP Caching — означення кешу, поділ на приватні й спільні, правила зберігання та відповідей.
- MDN — HTTP caching — керовані кеші (reverse proxy, CDN, Service Worker) і евристичне кешування без
Cache-Control.
Життєвий цикл запиту: у якій точці кеш перехоплює
- Chrome DevTools — Network features reference — фази запиту від резолвінгу імені до завантаження тіла.
- MDN — Critical rendering path — що відбувається з відповіддю після отримання й звідки беруться підресурси.
- RFC 9111 — HTTP Caching — перевикористання збереженої відповіді замість повторного походу мережею.
- MDN — Service Worker API — воркер як проксі між застосунком, браузером і мережею.
Заголовки, якими сервер керує кешем
- RFC 9111 — HTTP Caching — директиви
Cache-Control, порядок обчислення терміну свіжості, правило найсуворішої директиви й застереження проno-store. - MDN — HTTP caching — еквівалентність
max-age=0, must-revalidateіno-cacheразом із застереженням так не писати.
Свіжість проти валідації: умовний запит і 304
- RFC 9111 — HTTP Caching — свіжість і застарілість, умовний запит із валідатором, семантика
304 Not Modified. - MDN — HTTP caching — межа
no-cacheпри навігації по історії та bfcache. - MDN — HTTP conditional requests —
ETagяк непрозорий валідатор версії ресурсу.
Спільний кеш проти приватного: private, public, s-maxage
- RFC 9111 — HTTP Caching —
privateу простій і кваліфікованій формі, точне значенняpublic,s-maxageіAge. - MDN — HTTP caching —
Varyяк частина ключа кешу.
CDN як спільний кеш і що це змінює для тестування
- MDN Glossary — CDN — CDN як мережа проміжних серверів ближче до користувача й розвантаження origin.
- RFC 9111 — HTTP Caching — означення спільного кешу й правила, за якими живе будь-який проміжний вузол (самого терміна CDN у документі немає).
- MDN — HTTP caching — cache busting через зміну URL, фінгерпринтинг і пара «HTML з
no-cacheплюс асети зimmutable». - Playwright — Locators — чому локатор не будують на CSS, який змінюється разом із версткою.
Посередники: проксі, шлюз, тунель
- RFC 9110 — HTTP Semantics — три форми посередника та їхні визначальні ознаки, спільний кеш як мотив проксі,
interception proxy, різниця502/504і503.
Service Worker: кеш, який живе в коді сторінки
- MDN — Service Worker API — воркер як проксі, окремий потік, захищений контекст, життєвий цикл, стан очікування і
skipWaiting(),FetchEvent.respondWith(). - web.dev — The Offline Cookbook — Cache API як окреме сховище, спільне на весь origin, і канонічні назви трьох патернів разом з їхньою ціною.
Що видно у вкладці Network: позначки походження відповіді
- Chrome DevTools — Network features reference — зміст колонки
Size, прапорецьDisable cacheта область його дії, панельNetwork conditions.
Перехоплення мережі як спосіб обійти кеш у тесті
- Playwright — Network — спостереження й зміна трафіку, три рішення обробника, вибірковість перехоплення, дія контекстного маршруту, режим запису-відтворення через HAR.
- Playwright API — class: Route — ігнорування заборонених заголовків, зворотний порядок обробників, різниця
route.fallback()іroute.continue(). - Playwright API — class: Page — реєстрація маршруту й прибирання його після тесту.
Практичний наслідок: свіжий контекст на прогін
- Playwright — Test Isolation (browser contexts) — окремий контекст як найдешевша ізоляція стану між тестами.
- W3C — Service Workers —
unregister()діє лише на наступні навігації. - MDN — Service Worker API — реєстрація воркера і її зняття з боку сторінки.
- MDN — HTTP caching — механіка свіжості й ревалідації, яку такий тест і перевіряє.
- Chrome DevTools — Network features reference — вимкнення кешу як окремий свідомий режим, а не дефолт.
Що таке кешування в HTTP і хто вирішує, що зберігати?
— це збереження вже отриманої відповіді в проміжному сховищі, щоб наступного разу закрити такий самий запит без походу в мережу. Головне, що плутають: рішення ухвалює не браузер за власним бажанням, а сервер — через заголовки відповіді. Браузер лише виконує ці вказівки, тому кеш-поведінка є такою самою частиною контракту, як формат тіла чи код відповіді, і перевіряється звичайним на заголовок. Правила стосуються не тільки картинок і скриптів: відповідь API для HTTP нічим не особлива й підпадає під ті самі директиви. Окремо варто памʼятати контрінтуїтивне: мовчання в заголовках не дорівнює забороні — за певних умов кеш має право зберегти й перевикористати відповідь навіть без явних вказівок, це евристичне кешування. Практичний висновок для QA: розслідування «чому я бачу старе» починається з читання заголовків, а не з коду застосунку.
Чим приватний кеш відрізняється від спільного?
Приватний обслуговує рівно одного користувача й зазвичай живе всередині клієнта — це кеш браузера. Спільний стоїть на дорозі між багатьма користувачами й сервером: , , CDN. Різниця не академічна, а безпекова: персоналізована відповідь — баланс, ПІБ, чужий кошик, — яка осіла у спільному кеші, здатна поїхати іншій людині. Саме тому серед директив є окрема, що спільним кешам зберігати забороняє. Окремо виділяють керовані кеші (reverse proxy, CDN, Cache API у Service Worker): їх розгортає й налаштовує сама команда сервісу, тобто відповідальність за їхню конфігурацію — ваша, а не «десь у браузері».
Чим no-cache відрізняється від no-store?
Назва обманює: no-cache зберігати не забороняє. Копія лягає у сховище, просто перед кожним використанням її треба звірити з сервером — мережевий похід відбувається щоразу, зате тіло не передається, якщо ресурс не змінився. no-store — інша річ: зберігати не можна жодну частину запиту чи відповіді, тож і економити нема на чому, щоразу їде повне тіло. Практично no-cache ставлять там, де відповідь мусить бути актуальною щоразу, — класика тут HTML-оболонка застосунку, яка й приносить актуальні імена хешованих файлів; no-store лишають для даних, зберігати які не можна взагалі. І застереження, за яке дають бали: no-store не є засобом приватності, RFC прямо називає директиву ненадійною і недостатньою для цієї мети.
Чи правда, що no-cache — це просто інша назва для max-age=0, must-revalidate?
Наполовину, і сильна відповідь якраз розводить ці половини. MDN справді фіксує, що комбінацію часто вважають рівнозначною no-cache, — і там же радить її не вживати: max-age=0 у цій ролі є спадком реалізацій, давніших за HTTP/1.1, які директиви no-cache не вміли обробляти. RFC 9111 такої тотожності не встановлює: вона існує на рівні опису MDN, а не специфікації. Тому коректне формулювання звучить так — збіг є, джерело в нього одне, підстав писати так у новому коді немає.
Як кеш обчислює, скільки відповідь лишається свіжою?
Він бере перше, що знайде, у фіксованому порядку: s-maxage (але лише якщо кеш сам спільний), далі max-age, далі різницю між Expires і Date. Звідси відповідь на класичне питання «що переможе, max-age чи Expires» — відносний max-age, бо його перевіряють раніше. Якщо директиви суперечать одна одній, кеш зобовʼязаний виконати найсуворішу. Поки вік копії не перевищив цей термін, вона свіжа й віддається без жодного мережевого запиту; щойно термін вичерпано, копія стає застарілою і без перевірки її віддавати не можна.
Що таке умовний запит і що насправді означає 304?
Коли копія застаріла, кеш її не викидає, а йде питати сервер, чи вона ще годиться. Питання формулюється валідатором: збережене значення ETag клієнт відправляє заголовком If-None-Match, а дату з Last-Modified — заголовком If-Modified-Since. Якщо на сервері та сама версія, у відповідь приходить 304 Not Modified без тіла: кеш освіжає метадані своєї копії й віддає її далі. Виграш тут не в кількості запитів — запит таки відбувся, — а в тому, що мережею не поїхало тіло. У мережевій панелі 304 треба читати як «ревалідація працює», а не як помилку.
Чим ETag відрізняється від Last-Modified і чому ETag не можна розбирати?
Last-Modified — це дата останньої зміни, і в умовному запиті вона їде заголовком If-Modified-Since. ETag — непрозорий ідентифікатор версії ресурсу, який їде в If-None-Match: його зміст належить серверу, а клієнт має право лише порівнювати значення, а не тлумачити їх. Непрозорість — не побажання, а правило: витягати з ETag хеш, номер ревізії чи довжину заборонено. Для автотестів це дає конкретну межу: асертити можна наявність заголовка й те, що після зміни ресурсу значення стало іншим, а от парсити його всередині — самообман, який зламається на першій же зміні реалізації бекенду.
Що насправді означає директива public?
Не «дозвіл спільним кешам» — саме так її переказують найчастіше, і це помилка. public знімає заборону зберігати для будь-якого кешу: відповідь позначається придатною для збереження навіть там, де інакше це було б заборонено. Відповідь на запит із заголовком Authorization — приклад такої знятої заборони, а не зміст самої директиви. Розмежовує ж кеші private: спільному зберігати таку відповідь не можна, приватному — можна. Існує ще кваліфікована форма private, у якій прямо перелічують імена полів: під заборону для спільного кешу потрапляють тільки ці заголовки, а все інше повідомлення він зберегти може. Поруч працює s-maxage: він задає окремий строк саме для спільних кешів, тож CDN може тримати відповідь довго, а браузер користувача — коротко; звідси й упізнаваний симптом «локально свіже, на стенді старе».
Що показує заголовок Age і в чому тут типова помилка?
Age показує приблизний вік відповіді в секундах, і відлік починається з моменту, коли її створили або ревалідували на origin-сервері. Хибне прочитання, яке зустрічається постійно, — «скільки вона пролежала в CDN»: відлік ведеться від події на джерелі, а не від моменту потрапляння у конкретний вузол. Практична користь від заголовка все одно велика: разом із max-age він відповідає на питання «мені щойно віддали свіже чи вже полежале». Якщо Age підповз до значення max-age, копія ось-ось стане застарілою, і наступний запит уже піде на ревалідацію.
Навіщо потрібен Vary і чим шкідливий надто широкий Vary?
Vary перелічує заголовки запиту, які входять до ключа кешу: збережену відповідь віддадуть лише тому запиту, у якого ці заголовки збігаються. Класичні застосування — Accept-Encoding і Accept-Language: стиснена й нестиснена, англомовна й україномовна версії не повинні підмінювати одна одну. Зворотний бік — надто широкий ключ: Vary: User-Agent фактично множить ключ на кількість версій браузерів, тож кеш роздувається копіями, які майже не перевикористовуються. Для тестів це ще й пояснення дивних розбіжностей між клієнтами: два запити з різними значеннями таких заголовків законно отримують різні копії однієї й тієї самої відповіді.
У панелі Network один запит показує (from disk cache), інший — 304. Це один діагноз?
Ні, і плутанина тут дорого коштує. Рядок із позначкою (from memory cache) або (from disk cache) читається однозначно: запиту не було взагалі — копія ще не вичерпала свій строк, тож браузер обійшовся сховищем і до сервера не звертався. Наслідок неприємний: оновлення з бекенду не дійде до клієнта, поки цей строк не мине. 304 — протилежна картина: запит відбувся, сервер звірив валідатор і підтвердив копію, зекономивши тільки тіло. Перше пояснює скаргу «я задеплоїв, а нічого не змінилося», друге каже, що механізм працює як задумано. Колонка Size у цих рядках показує не байти, а відповіді, — це найшвидший спосіб визначити рівень, на якому запит обірвався.
Що таке cache busting і чому HTML віддають інакше, ніж бандли?
Cache busting розвʼязує суперечність «кешувати надовго, але бачити нове одразу» через зміну адреси: змінився вміст — змінився URL. Практична реалізація — фінгерпринтинг: до імені файлу підмішують хеш його вмісту (app.a1b2c3d4.js). Кожна збірка отримує власну адресу, старий запис у кеші просто перестає стосуватися справи, і примусово інвалідувати його не треба. Звідси двоскладова схема: документ лишають на no-cache, бо в ньому й лежить актуальний список адрес, а хешовані асети — на дуже довгому строку з immutable. Побічний нюанс, цінний для AQA: хеш в імені файлу — благо, а хеш в імені CSS-класу — джерело ламких , бо змінюється щобілду.
Що змінює наявність CDN для тестування?
CDN — мережа проміжних серверів ближче до користувача, і з погляду HTTP це звичайний спільний кеш: він слухає private, s-maxage і решту директив за тими самими правилами. Різниця для QA в іншому: зʼявляється рівень, на який тестовий код не має жодного впливу. Кеш браузера можна вимкнути, — заблокувати, а вузол CDN стоїть на чужій машині в чужому регіоні, і «почистити» його з тесту неможливо. Звідси характерний симптом: локально свіже, на стенді стабільно старе — підозра лягає на CDN раніше, ніж на код. Лікується це процесом — примусове скидання вузлів одразу після релізу, звірка версії збірки до того, як стартувала, — і перехопленням там, де потрібен детермінізм; але не паузами, бо пауза перетворює системну проблему на плаваючий .
Чим шлюз відрізняється від проксі і навіщо це тестувальнику?
Визначальна ознака проксі — його обирає клієнт: компанія навмисно проганяє весь трафік через один вузол, щоб мати на ньому безпекові перевірки або один кеш на всіх. Шлюз (він же reverse proxy) влаштований дзеркально: для зовнішнього зʼєднання він поводиться як origin-сервер, а що саме за ним — кеш-прискорювач, розподіл навантаження, кілька машин — клієнт не бачить. Третя форма — : сліпий ретранслятор, який повідомлень не змінює і учасником HTTP-обміну не вважається. Практичний бонус для баг-репортів: 502 і 504 за означенням віддає вузол у ролі шлюзу чи проксі, тобто зламалася ланка за , тоді як 503 каже, що сам сервіс тимчасово не тягне. І окремо про мережі, де вашої згоди не питали: вузол, що фільтрує трафік без відома відправника, протокольно не відрізняється від на шляху, — тому в корпоративній мережі чи на публічному Wi-Fi запит може повестися інакше без жодної зміни в застосунку.
Що таке Service Worker і чому його кеш не лікується заголовками?
Це JavaScript, який браузер виконує у фоні окремо від сторінки, у власному потоці й без доступу до DOM; за роллю він є проксі між застосунком, браузером і мережею та перехоплює навігаційні й ресурсні запити. Працює лише в захищеному контексті — HTTPS, з винятком для http://localhost. Головне для QA: сховище в нього своє, Cache API, і з HTTP-кешем браузера воно не повʼязане; віддача звідти відбувається явно, у коді обробника, а не автоматично. Тобто бездоганні Cache-Control на бекенді на вміст цього сховища не впливають — воркер поверне те, що йому наказали повернути. Звідси й порядок підозр: заголовки перевірені, а примара старої версії лишилася — дивіться на воркер.
Чому після деплою користувач може лишатися на старій версії воркера?
Через життєвий цикл. Браузер бере файл воркера на встановлення у двох випадках: коли бачить його вперше і коли виявляє байтову різницю з тим, що вже стоїть; на етапі install зазвичай наповнюють кеш, на activate — прибирають застаріле. Але якщо сторінку вже контролює активний воркер, нова версія встановлюється у фоні й зупиняється в стані очікування: контроль вона візьме лише після закриття всіх вкладок сайту — або одразу, якщо код сам викликав skipWaiting(). Для тесту це означає, що після логіка кешування цілком може лишатися старою, і звичайне перезавантаження сторінки цього не змінює. Тому «оновив сторінку — маю бачити нову версію» без явного skipWaiting() не є валідним очікуванням.
Назвіть патерни віддачі з кешу у воркері та ціну кожного.
Перший — cache-first (у документації «Cache, falling back to network»): беремо зі сховища, у мережу йдемо лише за відсутності копії; ціна — залипання на старій версії після деплою. Другий — network-first («Network falling back to cache»): спершу мережа, кеш як запасний варіант; ціна — на поганому звʼязку користувач чекає, поки мережа впаде. Третій — stale-while-revalidate: збережене віддається негайно, а оновлення підтягується у фоні, тож свіжий вміст користувач побачить наступного разу. Для тестів це три різні очікування: із cache-first асерт «після деплою одразу нове» законно провалиться, а зі stale-while-revalidate новий вміст зʼявиться лише на другому заході.
Як зробити тест незалежним від кешу, не чіпаючи самих сховищ?
Перехопити мережу — тобто перестати гадати про вміст сховищ і самому визначити, що прийде у відповідь. Playwright дає маршрути, у яких обробник має три можливі рішення: віддати запит на сервер, за потреби переписавши адресу, метод, заголовки чи тіло; підмінити відповідь власною, і тоді сервера ніхто не турбує; або обірвати запит. Перехоплення вибіркове: усе, що під шаблон не підпало, іде звичним шляхом, а маршрут, зареєстрований на рівні контексту, поширюється й на попапи. Дві пастки варто назвати вголос: заборонені заголовки на кшталт Cookie чи Host підмінити не вийде, override просто проігнорують — і тест зеленітиме на непідміненому запиті; а якщо на один шаблон повісити кілька обробників, спрацюють вони у зворотному до реєстрації порядку, причому передати керування далі можна лише через route.fallback(), бо route.continue() ланцюг завершує. Коли рукотворні відповіді виходять нереалістичними, є режим запису й відтворення трафіку через HAR.
Як ізолювати тести від кешу — і коли цього робити не можна?
Найдешевший — свіжий профіль на прогін; у Playwright цю роль грає окремий browser context, який не тягне за собою накопичений стан, і робиться це без жодного коду очищення. Воркер доводиться прибирати окремою дією, бо його сховище лежить поза HTTP-кешем, і тут є нюанс, що коштує годин: зняття реєстрації діє лише на наступні навігації, тож у поточному контексті воркер після виклику ще працює. Надійніше взагалі не дати йому зареєструватися — опцією контексту serviceWorkers: 'block'. І межа, без якої все це перетворюється на ритуал: глушити кеш у кожному тесті — помилка. Якщо предметом перевірки є саме кешування — що умовний запит повертає 304, що офлайн-режим працює, — кеш мусить бути ввімкнений, інакше ви перевіряєте застосунок, якого в проді не існує.
«Тест бачить стару версію. Ваші дії?»
Спершу визначити рівень, а не правити код — саме порядок кроків тут і слухають. Крок перший, мережева панель: (from disk cache) означає, що запиту не було зовсім, 304 — що ревалідація відпрацювала, живий 200 — що тіло справді поїхало мережею, але віддати його міг і спільний кеш, а не origin. Крок другий — Service Worker: якщо він зареєстрований, правильні HTTP-заголовки нічого не доводять. Крок третій — спільний кеш: стабільно старе на стенді при свіжому локально вказує на CDN або проксі. І лише коли всі три рівні відкинуто, підозра переходить на застосунок — його стан у памʼяті, сховище браузера чи кеш HTTP-клієнта. Саме тому фраза «почистив кеш» нічого не означає як процедура: з неї не зрозуміло ні про який рівень ідеться, ні чим і коли його чистили, а до двох рівнів із чотирьох браузерна кнопка взагалі не дотягується.
Три кейси про той самий симптом із різних боків: як за хвилину визначити рівень, з якого прийшла стара відповідь; як заасертити кеш-контракт замість того, щоб на нього сподіватися; і як зробити тест детермінованим, не вимикаючи кеш там, де він і є предметом перевірки.
Кейс 1. «На стенді стара версія»: діагностика за рівнями
Тест не знаходить нову кнопку, у розробника все свіже. Перш ніж чіпати , треба зʼясувати, який саме рівень віддав відповідь: сховищ між сторінкою і сервером кілька, і лікуються вони по-різному. Порядок огляду завжди однаковий — від найближчого до сторінки до найдальшого.
| Що видно | Рівень-підозрюваний | Дешева перевірка |
|---|---|---|
(from memory cache) або (from disk cache) у колонці Size | приватний HTTP-кеш браузера | прогін у новому browser context; Disable cache у панелі мережі |
304 на статиці, вміст усе одно старий | ревалідація працює — проблема не тут | звірити імена файлів у HTML: там може стояти старий бандл |
| заголовки відповіді правильні, а вміст усе одно старий | Service Worker і його Cache API | прогін із serviceWorkers: 'block': примара зникла — винен воркер |
живий 200, але вміст лежалий | спільний кеш — CDN або проксі | той самий URL повз CDN; провайдерський заголовок статусу кешу |
| локально свіже, на стенді стабільно старе | CDN або проміжний проксі | перевірити версію застосунку перед стартом сюїти |
| у новому контексті свіже, у робочому профілі старе | локальний рівень: кеш, воркер або сховище браузера | чистий профіль проти робочого — різниця вкаже на рівень |
Три висновки, які економлять години.
(from disk cache)і304— різні діагнози, а не синоніми «кеш спрацював». У першому випадку мережі не було зовсім: строк ще не вичерпано, тож оновлення з бекенду до клієнта просто не дійде, скільки б разів тест не перезавантажив сторінку. У другому запит відбувся, сервер підтвердив копію і зекономив лише тіло — тобто механізм справний, і причину треба шукати далі.- Правильні заголовки нічого не доводять, поки не перевірено . Cache API — окреме сховище, на яке
Cache-Controlбекенду не впливає. Якщо воркер зареєстрований, він міг віддати збережену копію, навіть коли з сервером усе бездоганно. - «Почистив кеш» — не крок у баг-репорті. З неї не видно ні рівня, ні інструмента, а до двох рівнів із чотирьох браузерна кнопка й не дотягується: воркер знімається окремо і не миттєво, а спільний кеш узагалі живе на чужій машині. Відтворюваний запис звучить інакше: «свіжий контекст, воркери заблоковані, версія бандла в HTML — така-то».
Кейс 2. Кеш-контракт як предмет асерту
Заголовки кешу — така сама домовленість між бекендом і фронтендом, як формат тіла. Її можна перевіряти без браузера, через request: тут не потрібні ні кліки, ні рендер.
import { test, expect } from '@playwright/test';
test('HTML-оболонка ревалідується щоразу', async ({ request }) => {
const res = await request.get('/');
// саме HTML приносить актуальні імена хешованих файлів
expect(res.headers()['cache-control']).toContain('no-cache');
});
test('хешований бандл кешується надовго', async ({ request }) => {
const res = await request.get('/assets/app.a1b2c3d4.js');
const cc = res.headers()['cache-control'] ?? '';
expect(cc).toContain('immutable');
expect(cc).toMatch(/max-age=\d{8,}/); // вісім цифр — від кількох місяців і більше
});
test('персоналізована відповідь не осяде у спільному кеші', async ({ request }) => {
const res = await request.get('/api/me', {
headers: { Authorization: `Bearer ${process.env.USER_TOKEN}` },
});
const cc = res.headers()['cache-control'] ?? '';
expect(cc, 'відповідь із даними користувача мусить бути закрита для спільних кешів')
.toMatch(/private|no-store/);
});
Ревалідацію перевіряють умовним запитом: спершу забирають валідатор, потім повертаються з ним і чекають 304. Це той рідкісний тест, де кеш вимикати категорично не можна, — він і є предметом перевірки.
test('умовний запит із валідатором повертає 304 без тіла', async ({ request }) => {
const first = await request.get('/assets/app.a1b2c3d4.js');
const etag = first.headers()['etag'];
expect(etag, 'без ETag ревалідації не буде').toBeTruthy();
const second = await request.get('/assets/app.a1b2c3d4.js', {
headers: { 'If-None-Match': etag },
});
expect(second.status()).toBe(304);
expect(await second.text(), '304 не несе тіла').toBe('');
});
Що дивитися і чому:
no-cacheна HTML — не «вимкнули кеш», а свідома пара доimmutableна асетах. Схема тримається на тому, що HTML щоразу приносить свіжі імена файлів; варто закешувати його надовго — і користувач годинами тягтиме старі бандли за старими адресами.- на
privateдля персоналізованих відповідей — це тест безпеки, а не продуктивності. Пропущена директива тут дає не повільну сторінку, а чужі дані в спільному кеші. ETagперевіряють на факт і на зміну, а не на вміст. Значення непрозоре: правильний асерт — «заголовок є» і «після зміни ресурсу став іншим». Розбір його на частини зламається від першої ж зміни реалізації на бекенді.Ageу відповіді читають обережно. Він відлічує час від генерації або ревалідації на origin, а не строк лежання в конкретному вузлі; асерт «Ageменший за N» перевіряє саме вік відповіді, а не географію вузла, який її віддав.
Кейс 3. Детермінізм без ритуалів: контекст, воркер, маршрут
Продуктовий тест не мусить залежати від того, що встигло осісти у сховищах. Три рівні контролю закривають це майже повністю: свіжий контекст прибирає накопичений стан, блокування воркера знімає найпідступніший рівень, перехоплення робить відповідь передбачуваною.
import { test, expect } from '@playwright/test';
// воркеру не дають зареєструватися — його кеш у цьому прогоні просто не існує
test.use({ serviceWorkers: 'block' });
test('банер показує тариф із підміненої відповіді', async ({ page }) => {
// маршрут ставимо ДО навігації: правило діє лише на запити після реєстрації
await page.route('**/api/pricing', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ plan: 'pro', price: 49 }),
}),
);
await page.goto('/pricing');
await expect(page.getByTestId('plan-price')).toHaveText('49');
// маршрут, залишений після тесту, перехоплюватиме запити наступних у тому ж воркері
await page.unroute('**/api/pricing');
});
А ось спроба, яка виглядає робочою й мовчки не працює: підміна забороненого заголовка.
// НЕ спрацює: Cookie у списку заборонених — override просто проігнорують,
// запит поїде з реальними cookie, а тест зеленітиме на непідміненому запиті
await page.route('**/api/**', (route) =>
route.continue({
headers: { ...route.request().headers(), cookie: 'role=admin' },
}),
);
Що дивитися і чому:
- Свіжий browser context — найдешевша ізоляція. Він не тягне ні HTTP-кеш, ні сховища попереднього прогону, і для цього не потрібно жодного коду очищення.
serviceWorkers: 'block'надійніший за зняття реєстрації. Скасування реєстрації діє лише на наступні навігації — у поточному контексті воркер після виклику ще працює і ще віддає своє. Заборона на рівні контексту знімає цю гонку повністю.- Порядок «спершу маршрут, потім дія» не косметичний. Перехоплення поширюється тільки на запити, надіслані після реєстрації обробника; маршрут, поставлений після
goto, першу порцію запитів пропустить. - Кілька обробників на один шаблон спрацьовують у зворотному порядку. Останній зареєстрований перекриває попередні, а далі по ланцюгу веде тільки
route.fallback();route.continue()на цьому місці ланцюг обриває. - Блокувати кеш «про всяк випадок» у всій не варто. Тести з кейсу 2 вимагають увімкненого , інакше вони перевіряють неіснуючий режим роботи застосунку. Розділяйте: детермінізм — для продуктових сценаріїв, реальний кеш — для тестів самого кешу.
Хто кешує і на якій підставі
- Можу пояснити, чому правила диктує сервер заголовками, а браузер їх лише виконує — і чому мовчання в заголовках не означає «кеш вимкнено» (евристичне ).
- Знаю різницю приватного vs спільного кешу і вмію назвати : персоналізована відповідь у спільному кеші може поїхати чужій людині.
Директиви Cache-Control
- Знаю різницю
no-cachevsno-store: перша дозволяє зберігати, але вимагає валідації щоразу; друга забороняє зберігання й не економить нічого. - Можу сказати, чому
no-storeне є механізмом приватності, і послатися на застереження RFC, а не на власну думку. - Розумію статус формули «
no-cache=max-age=0, must-revalidate»: еквівалентність фіксує MDN (і радить так не писати), специфікація директиви не ототожнює. - Знаю порядок обчислення терміну свіжості —
s-maxage, потімmax-age, потімExpiresмінусDate— і правило конфлікту: виконується найсуворіша директива.
Свіжість і валідація
- Можу розвести свіжу vs застарілу копію й описати механіку умовного запиту: збережений
ETagіде якIf-None-Match, дата — якIf-Modified-Since, а304читається «копія годиться, тіло не шлю». - Розумію, чому
ETagнепрозорий і чому має перевіряти зміну значення, а не його вміст. - Памʼятаю межу
no-cache: при навігації по історії ревалідація не гарантована, бо браузер може відновити сторінку з bfcache.
Спільний кеш, CDN і посередники
- Знаю різницю
privatevspublic(перша розмежовує кеші, друга лише знімає заборону зберігати — для будь-якого) і навіщо поруч існуєs-maxage. - Читаю два дебаг-заголовки без помилок:
Ageвідлічує час від генерації або ревалідації на origin, а не строк лежання у вузлі;Varyдодає перелічені поля до ключа кешу, і надто широкий (User-Agent) роздуває кеш копіями, які майже не перевикористовуються. - Знаю різницю vs (перший обирає клієнт, другий видає себе за origin) і памʼятаю, що
502і504віддає , а503— сам сервіс. - Можу пояснити cache busting через зміну URL: хеш вмісту в імені файлу плюс пара «HTML з
no-cache, асети зimmutable».
Service Worker і Cache API
- Знаю різницю Cache API vs HTTP-кеш браузера: це окремі сховища, тому коректні заголовки бекенду вміст воркерського кешу не змінюють.
- Можу описати життєвий цикл і стан очікування: нова версія встановилася, але контроль бере лише після закриття всіх вкладок або після
skipWaiting(). - Розрізняю cache-first, network-first і stale-while-revalidate і можу назвати ціну кожного патерну для тестового очікування.
Діагностика й ізоляція тестів
- Не плутаю
(from disk cache)vs304: у першому випадку мережею не пішло нічого, у другому запит відбувся і ревалідація спрацювала. - Можу назвати порядок діагностики «бачу стару версію» — рівень у мережевій панелі, , спільний кеш, і лише тоді код — і пояснити, чому «почистив кеш» не є відтворюваною процедурою, а свіжий browser context із заблокованим воркером є.
- Знаю три рішення обробника маршруту (пропустити, підмінити, обірвати) і дві пастки: заборонені заголовки не підміняються, а кілька обробників спрацьовують у зворотному до реєстрації порядку.
- Памʼятаю виняток із правила ізоляції: якщо предмет перевірки — саме кешування, вимикати кеш не можна.
Квіз
Перед стартом
- Питань: 15
- Поріг «зараховано»: ≥70% правильних відповідей.
- Результат впливає на прогрес; завалені питання підуть у чергу повторення.
- Квіз впливає на компліт теми: тема стає «пройдено», лише коли прочитано теорію І квіз складено на ≥70%.
Питання
Чим приватний кеш відрізняється від спільного?