Сучасна диджитал-освіта для дітей — безоплатне заняття в GoITeens ×
Mazda CX 30
×

Кешування наперед, або Сповідь адепта WorkBox

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті

Усім привіт, я Армен Айвазян, Front-End Developer в міжнародній ІТ-компанії АМО. У цій статті розповім про кешування наперед та як в цьому може допомогти WorkBox.

У статті я опишу реальний кейс реалізації на сайті AmoMama. Я навмисне опускаю багато технічного контексту, який можна легко нагуглити (наприклад: що ми юзаємо Next.js, або глибокі копання в конкретних інструментах і термінах). В цій статті я хочу описати саме концептуально ідею, яку можна буде застосувати на будь-якому проєкті, щоб дати вам один з можливих варіантів вирішення проблеми.

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

Ідея

Додам трохи контексту: AmoMama — це міжнародна мультимедійна платформа, частина міжнародної IT-компанії AMO, яка створює контент у ніші розважального сторітелінгу. Аудиторія сайту оцінюється в понад 40 мільйонів людей щомісяця.

Як і будь-яка історія, ця наша теж почалась з шаленої ідеї: «Чи можна зробити перехід між сторінками непомітним для користувача?». Хотілося реалізувати чудовий UX, щоб юзер клікнув на статтю і одразу побачив її зміст, а не йшов заварювати собі чай (умовно), поки перша картинка та заголовок завантажаться. Жартома, у нас в команді, ця фіча отримала назву — «безшовний перехід» або, науково, — seamless experience. Звучало потужно, просто, одним словом — багатообіцяюче.

Тому кому цікаво, чим це все закінчилося — «ласкаво прошу під кат!»

Перші кроки

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

Якщо простими словами: замість того, щоб наш клієнт кожен раз ходив за одним і тим самим файлом (наприклад, картинкою милого котика) на сервер, в процесі/ після першого звернення за файлом ми зберігаємо дані у клієнтському браузері. І коли наступний раз наш юзер захоче отримати файл (порцію милування), він не буде чекати довгої відповіді з сервера, а буквально одразу отримає його зі свого браузеру (вебкешу). А кешувати дані наперед, значить все те саме, тільки файл повинен опинитися в кеші клієнта ще до того, як клієнт захоче отримати цей файл (так, звучить магічно, але ж хто не любить магічні пригоди?).

Нашим першим кандидатом був стильний-молодіжний Service Worker. Він працює в окремому потоці, може перехоплювати запити, в перспективі за його допомогою можна реалізувати офлайн-режим — ну, не інструмент, а мрія під нашу задачу. План мінімум:

  1. юзер клікає на пост;
  2. fetch-запит йде на API;
  3. Service Worker перехоплює його та замість довгого походу на API повертає кешовані дані.

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

Ліричний відступ. У одного з наших конкурентів сторінки відкривались моментально і в DevTools, у вкладці Application, можна було побачити активний Service Worker. В цей момент ми остаточно переконались, що Service Worker — це правильний вибір.

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

function serviceWorker(cacheName, cacheUrls) {
 self.addEventListener('fetch', function (event) {});
}
serviceWorker('sw', ['/']);

Все вірно: цей код не робить рівним рахунком нічого! Але нас вже було не зупинити і ми почали реалізацію нашої першої версії «безшовного переходу».

Перший млинець нанівець

Але перед історією першої реалізації, включу на трошки режим роз’яснювальної бригади: класична суть кешування Service Worker’ом (Cache first, falling back to network) в тому, що:

  • юзер робить запит за даними;
  • воркер перевіряє, чи є ці дані в кеші;
    1. якщо немає: ходить «в інтернет» та віддає їх користувачеві, а сам кешує їх на клієнті для наступного разу;
    2. якщо є: бере їх з кешу і повертає;
  • юзер отримує дані.

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

1. Як нам взагалі посилати запити, щоб кешувати їх, до того як користувач вирішить почитати статтю? Бо в таких паблішерів, як ми, важливий саме перший захід, адже юзери дуже рідко переходять на ті самі статті ще раз, зазвичай користувач читає статтю і більше до неї не повертається. Тому нам не підходила така модель, нам потрібно було зробити так, щоб юзер отримав закешований запит з першого кліку. Що ж робити? Правильно! Нестандартні задачі потребують нестандартних рішень!

Приймається дивне, але таке логічне, рішення — робити запит за клієнта, поки він про це ще не знає.

Стає зрозумілий ключовий момент всієї цієї історії — не Service Worker’ом єдиним. Адже наш SW не вміє в DOM. Йому буквально про нього нічого невідомо (адже, як ми знаємо, він живе у своєму, окремому скоупі). Він не може дивитися на лінки нашої сторінки і «відправляти запити з коду». Та тут на арену виходить наш hook (який на цей момент ще просто функція pushSWLink), він «парсить» сторінку, на якій знаходиться юзер, і у фоні відправляє запити на API, які, своєю чергою, вже перехоплює наш Service Worker та кешує. А юзеру залишаються, так би мовити, «вершки» і він отримує дані одразу з кешу.

Реалізація нашого воркера до болю проста, щось на кшталт такого:

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches
      .open(PRECACHE)
      .then((cache) => cache.addAll(PRECACHE_URLS))
      .then(self.skipWaiting()),
  );
});
 
self.addEventListener('fetch', (event) => {
  if (event.request.url.startsWith(self.location.origin)) {
    event.respondWith(
      caches.match(event.request).then((cachedResponse) => {
        if (cachedResponse) {
          return cachedResponse;
        }
        return fetch(event.request).then((response) => {
          return response;
        });
      }),
    );
  }
});
  1. При ініціалізації (install): прекешуємо якісь файли (одна із фішок Service Worker, яка дає можливість кешувати файли одразу після завантаження сторінки).
  2. Навішуємо слухач на запити (fetch): при отриманні такого запиту, дивимось, чи є в кеші ці дані:
    • якщо немає — йдемо на API,
    • якщо є — повертаємо їх з кешу.

Виходить стандартна схема роботи Service Worker’а із запитами і їх кешуванням.
І реалізація записування в кеш теж вийшла простенька (hook):

export function pushSWLink(link: string, id: string): void {
 caches.open(`posts-data`).then((cache) => {
   cache.addAll([
     `/_next/data/${id}${link}.json?slug=${link.replace(/^\//, '')}`,
   ]);
 });
 caches.open(`posts`).then((cache) => {
   cache.addAll([`${link}/`]);
 });
}
  1. Отримуємо лінку на запит (link).
  2. Кешуємо спочатку самі дані з API (перший cache.addAll).
  3. Кешуємо всю сторінку (другий cache.addAll) (це повʼязано з технологіями, які ми використовуємо, а саме Next.js, тому нам потрібно кешувати і дані, і всю сторінку).

Дивовижно, правда? Ось і ми так подумали. Ех, якби ж, адже це — тільки початок нашої історії.

2. Ми «задудосили» нашу API. В середньому, на сторінці по 17 лінків на інші статті, а отже не складно підрахувати, яку кількість запитів ми отримуємо, навіть якщо лише 1000 користувачів одночасно зайдуть на сторінку і наш hook почне з кожного клієнта відправляти по 17 майже одночасних запитів.

І тут наш hook отримує +1 до апгрейдів, і тепер він має скіл рекурсивних запитів (поки не завершиться попередній, до наступного не переходимо).

const applyCache = (
 data: Array<{ name: string; path: string }>,
 counter = 0,
): void => {
 const { name, path } = data[counter];
 const isNotLastItem = counter < data.length - 1;
 
 caches.open(name).then((cache) => {
   cache.match(path).then((response) => {
     if (!response) {
       cache.add(path).then(() => {
         if (isNotLastItem) applyCache(data, (counter += 1));
       });
     } else if (isNotLastItem) applyCache(data, (counter += 1));
   });
 });
};

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

3. Ми закешували build скрипти. В якийсь момент ми помітили, що сторінки починають крашитись та видають дивні помилки. Як виявилось, коли кешуєш сторінку, не варто забувати, що ця сторінка містить посилання на js-build-срипти фреймворка.

І після пари деплоїв ми отримали таку картину: у юзерів, які закешували сторінку, у якої було посилання на старий build-скрипт не могли його дістати, адже після деплою файл змінився, а старий «помер». Тому ми були вимушені відмовитися від кешування сторінок і обмежитися лише кешуванням даних.

4. В «інтернетах» виявилось дуже мало інформації. На той момент на цю тему інформації в інтернеті майже не було. Це дуже ускладнювало задачу і нам доводилось набивати свої синці і складати цілісну картину з обрізків інформації.

5. А якщо юзер все ж таки вирішить повернутись на той самий пост, а він вже 10 разів оновився? Цю проблему ми просто записали, але вирішити на той момент так і не змогли.

І, нарешті, наш криптоніт.

6. Несумісність з іншими SW. У нас паралельним життям жив push notifications service (далі просто пушер, для зручності), який працював за допомогою Service Worker’а. І найбільшим відкриттям того часу для нас стало те, що на дикому заході може бути лише один шериф (на сайті — лише ОДИН SW).

А саме цікаве, будьте готові: вони не будуть викидати вам помилки, або переставати працювати через несумісність — ні. Вони будуть жити своїм життям та перехоплювати запити один одного, ніяк про це не пінгуючи. І тільки одному творцю Service Worker’ів відомо, як вони у себе там розрулюють, хто і що першим робить.

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

Розгерметизація старої ідеї та WorkBox

Через деякий час в поле наших радарів потрапляє такий собі товариш WorkBox.

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

  1. Можливість обʼєднувати багато Service Worker’ів під одним затишним дахом WorkBox (вирішувало проблему з пушером).
    • Також дає можливість майже з коробки реалізувати пушер в перспективі, відмовившись від нашого кастомного рішення.
  2. Зручна, проста і зрозуміла інвалідація кешу (вирішила проблему з оновленням постів).
  3. Зручні інструменти роботи з кешом та прекешом з коробки.
  4. Дуже багато фіч на перспективу. (офлайн-режим, гугл аналітика і тому подібні).

Ви тільки подивіться, як просто цей монстр з мільйоном фіч конфігурується (якщо відкинути інтеграцію з Next.js):

module.exports = {
 globDirectory: 'public/',
 globPatterns: ['**/*.{ico,svg}'],
 swDest: 'public/sw.js',
 swSrc: 'workbox/sw.js',
};

І все! Все інше — це вже ті деталі, в які я обіцяв не заглиблюватись. Позначу лише для тих, хто захоче колись скористатися цим гайдом, що існує два шляхи його використання:

  • generateSW;
  • injectManifest.

І це реально два РІЗНИХ підходи, тому спочатку виберіть, який з них підходить вам, а тільки потім починайте з цим роботу, бо якщо не в такій послідовності робити, то буде дуже боляче, — і цей висновок я зробив на досвіді.

Тепер наш скрипт, який відповідав за кешування/ запити виглядає приблизно ось так:

self.addEventListener('fetch', (e) => {
 const { request } = e;
 const { destination, method, mode, url } = request;
 
 if (destination !== '' && method !== 'GET' && mode !== 'cors') return;
 if (!url.includes('.json?slug=')) return;
 
 e.respondWith(
   caches.open('posts').then(async (cache) => {
     return cache.match(request).then((cachedResponse) => {
       const fetchedResponse = fetch(request).then((networkResponse) => {
         cache.put(request, networkResponse.clone());
 
         return networkResponse;
       });
 
       return cachedResponse || fetchedResponse;
     });
   }),
 );
});

Ідейна частина майже не змінилась, він став просто універсальнішим, обріс перевірками. Але нагадаю, що все це вже разом з пушером і більше вони не конфліктують.

А замість того, щоб лежати десь чистими js файлами в public папках, все це було в одному місці (оскільки WorkBox дозволяє писати скрипти окремими модулями, прямо на TS, і потім сам їх конвертує, мініфікує та «викидає» вже готовий один скрипт в public папку).

import { precacheAndRoute } from 'workbox-precaching';
 
import './workers/cache';
import './workers/pusher';
 
declare const self: ServiceWorkerGlobalScope;
 
self.addEventListener('install', () => {
 self.skipWaiting();
});
 
// eslint-disable-next-line no-underscore-dangle
precacheAndRoute(self.__WB_MANIFEST);

Тим часом наш «супер-hook», нікуди не зник, тільки трошки покращився (ми, наприклад, додали перевірку на offset, щоб тільки коли користувач доскролить до потрібного лінка, він закешував його), але, дотепер, він виконує ключову роль у всій цій пʼєсі.

Інформації по WorkBox виявилось ще менше. Майже все доводилось випробовувати методом спроб та помилок.

І перша із них — спроба застосувати на великому, живому та самодостатньому проєкті «чистий WorkBox», без надбудов. Поясню: WB має готові «пакети» для простої та зручної інтеграції, які і розраховані на такі кейси, коли тобі потрібно без зайвих рухів в конфігах next’а та webpack’а реалізувати у себе WorkBox. Але ми ж програмісти — круті перці, і кому потрібні ті надбудови, юзаємо чистий.

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

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

Ніби на підтвердження, що ми на правильному шляху, тоді вийшла конференція від Google «Page Experience Seminar for Publishers 2022 from Google Partner Hub»? в якій ми брали участь як паблішер, партнер Google. Там один з топіків був саме про WB, але ми розуміли, що пішли набагато глибше в його використанні, ніж той базовий приклад, який показували на презентації можливостей WorkBox.

Результати

І ось наш супер-hook + WorkBox нарешті побачили повноцінний продакшн.

1. Швидкість віддачі даних користувачеві до:

Після:

Швидше в 26 разів (на 50ms). Для юзера той самий, довгоочікуваний, «безшовний перехід».

2. Розмір бандла:

3. Пушер прекрасно уживається з нашим кешем.

4. Весь кеш інвалідується і зберігається на клієнті на необхідний нам час.

І для тих, хто все ж хоче трошки технічних деталей та більше практичності від цієї статті, ось як ці результати виглядали під капотом:

hook

// функція для рекурсивного відправлення запитів, щоб не дудосити API
async function fetchRequest(index: number, postsURL: string[]): Promise<void> {
 if (index + 1 > postsURL.length) return;
 
 // тут просто формуємо правильну урлу для запитів за даними
 const buildId = process.env.CONFIG_BUILD_ID;
 const url = postsURL[index];
 const slug = url.replace(/^\//, '');
 const json = `/_next/data/${buildId}/${slug}.json?slug=${slug}`;
 
 // сама логіка рекурсії
 if (!(await caches?.match(json))) {
   caches.open('posts').then(async (cache) => {
     try {
       await cache.add(json);
     } catch (e) {
       console.log('Failed Cache add', e);
     }
     return fetchRequest(index + 1, postsURL);
   });
 } else fetchRequest(index + 1, postsURL);
}
 
export function useSWCache(
 postsURL: string[], // урли на сторінки
): (element?: HTMLElement) => void {
 // Та сама первірка на offset, щоб робити запити в потрібний момент, а не всі одразу
 const { observe, inView } = useInView({
   unobserveOnEnter: true,
   rootMargin: `200px`,
 });
 
 useEffect(() => {
   if (inView) fetchRequest(0, postsURL);
 }, [inView]);
 
 return observe;
}

WorkBox

// Ось так просто додаємо наші SW
import './workers/pusher';
 
declare const self: ServiceWorkerGlobalScope;
 
// Автоматичне оновлення версії основного SW який хендлить всі процеси
skipWaiting();
clientsClaim();
 
// Для тих хто вибрав шлях гнучкості - injectManifest
const WB_MANIFEST = self.__WB_MANIFEST as { url: string; revision: string }[];
 
precacheAndRoute([]);
 
// Додає activate прослуховувач подій, який очищатиме несумісні попередні кешування, створені старішими версіями Workbox
cleanupOutdatedCaches();
 
// Основна логіка оброблювання fetch запитів
const postsCacheStrategy = new CacheFirst({
 cacheName: 'posts',
 plugins: [
   new ExpirationPlugin({
     // інвалідація кешу
     maxAgeSeconds: 60 * 10,
   }),
 ],
});
 
registerRoute(/\.json\?slug=/, postsCacheStrategy, 'GET');
 
// Потрібно, щоб всі інші запити, крім цільових, завжди йшли на API
setDefaultHandler(new NetworkOnly());

Конфігурація WB за допомогою next-pwa (якщо ваш стек Next.js, у іншому випадку, дивіться список):

const withPWA = require('next-pwa')({
 // папка де буде зберігатися основний SW
 dest: 'public',
 // файл реалізації WB
 swSrc: 'src/workbox/init.ts',
 dynamicStartUrl: false,
});

Висновок

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

Сподіваюсь, ця стаття хоч трошки вам допоможе не набити так багато синців, як це зробили ми. Оскільки у звʼязку зі «специфічністю» задачі ми не використали потенціал WB навіть на пару відсотків, у нашої команди багато планів на нього: додати офлайн-режим, перенести гугл аналітику у WorkBox, переписати пушер як пакет боксу — і це тільки поверхня айсберга.

А цією статтею я лише хотів сказати: неординарні ідеї, в правильному середовищі, можуть і переростають у чудовий UX.

Також, якщо вам цікаво дізнатися більше про перформанс і те, які експерименти ми ставимо на наших проєктах, у нас ще є два цікавих матеріали на DOU: «Як покращити показники Google Core Web Vitals на прикладі мультимедійної платформи» та «Як з якісним моніторингом досягти показників перформансу сайту на рівні 95+».

Дякую всім за увагу!

👍ПодобаєтьсяСподобалось10
До обраногоВ обраному11
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

При першому заході у вас доволі високий TTB, > 700 ms i.imgur.com/kvK3W0A.png

Це через SSR в next.js?

Зараз ми, як раз, працюємо над покарщенням цього показника. Це комплесна проблема, але так, вона в тому числі включає в себе витікаючі наслідки SSR моделі.

Якщо ж ми говоримо про сервіс воркери, то вони ніяк не впливають на цей показник (в нашому випадку).

А як на рахунок ліміту на к-сть запитів у фоні? Якщо сторінка на яку перейшов користувач буде містити 100 посилань на інші сторінки сайту, або 1000 (що малоймовірно, але все ж...)?

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

Але погоджуюсь, якби ми не мали впевненості в кількості лінків, варто було б розглянути штучний максимум закешованих даних і чи точно воно того варте : )

5. А якщо юзер все ж таки вирішить повернутись на той самий пост, а він вже 10 разів оновився? Цю проблему ми просто записали, але вирішити на той момент так і не змогли.

Пихнуть forecast update?

Перепрошую?) Щось не зрозумів що саме маєте на увазі)

Стаття супер. Сам теж впроваджував SW з Workbox на проекті. Зупинився на кешуванні ассетів (js / css / fonts / images) та декількох гет ендпоінтів з stale-while-revalidate. Дуже хотів кешувати геть усе, бо специфіка проекту така, що дані змінюються нечасто, але нічого не вийшло, бо graphql, зараза, на все робить POST ріквести, а їх сервіс воркер класти в cache store відмовляється. Єдина опція — це класти перехоплені респонси в indexedDB, але тоді чомусь апка чекає на ревалідейт ріквест замість того, щоб одразу використовувати відповідь з кешу, якщо така є. Навіть перший раз в житті створив запитання на стек оверфлоу, але марно: stackoverflow.com/...​idation-in-service-worker

Але загалом і так добре. Кешування ассетів сильно покращило візуальне враження та швидкість.

Дякую, дуже приємно що сподобалось! : )

Кешування з SW таке, згідний, хочеться відразу і все в нього засунути ; )

Хм, а на якому саме етапі виникла проблема з POST запитами, на запиті чи на відповіді? Бо ми ми робили POST запити та зберігали в кеш відповідь, наче проблем не було.
О це ви крутий шлях джедая обрали — кешувати і тягнути з indexedDB : ) Взагалі indexedDB воркерам потрібен для зберігання невеликих значень (рідка кейси) та глобальних змінних (часта історія) (таких як хеш для інвалідації, або версійність), тому, думаю, у Вас і виникли проблеми з кешування туди)

Згідний на всі 100%, будь яке кешування за допомогою SW дає сайту +1000 до крутого UX ! : )

Проблема була на етапі відповіді, воно їх просто не кешує, на це є навіть ішью в репозиторії W3C ServiceWorker: github.com/...​/ServiceWorker/issues/693
Ідею з кешуванням в IDB я поцупив чи звідти, чи зі стек оверфлоу, і воно ж типу працювало, але сенсу від того було небагато :)

Так, дійсно, не бачив такої проблеми, тут кейс з offline-first, у нас просто не зустрічався. А робота з SW вона така, підступна, завжди є якісь «підводні камені» : )

Несумісність з іншими SW. (на сайті — лише ОДИН SW).

Це хибне твердження. Не може бути два воркери на 1му скоупі. Також це згадується і в дискусії на stackoverflow (лінк є в статті).

Чи бачили Ви реалізацію багатьох Service Worker’ів, на реальному сайті (навіть в різних скоупах)?

Ви праві, дякую!

Я знав що в різних скоупах можна реєструвати багато SW, але в статті так написав спираючись на наш сумний досвід реалізації двох SW (в різних папках), які вели себе непрогнозовано, та конфліктували. На той момент не знайшли нормальної інформації як їх дружити та в чому може бути проблема. Але, раз навіть Google користується такою можливістю, значить треба провести додатковий ресьорч на цю тему (хоча на даний момент і не дуже актуально, так як ми використовуємо для менеджменту багатьох SW Workbox, а в ньому доволі просто все красиво організувати, але ніколи не пізно дізнатись більше! :) ), дякую!)

Нема за що, також потрібно памятати, що SW це JS, JS — однопоточний. Декілька SW теоритично дає вам декілька потоків (не первіряв).

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

1. Чимось (в даному випадку завантаженням пари data-джейсонок у фоні) приходиться жертвувати заради крутого UX, ми розцінили це, як невелику плату за такий досвід : )
2. Якщо такий плагін від браузерів і побачить світ, я думаю це буде удар не тільки для нас, а і для Next.js у якого є схожа концепція «Route prefetching», да і в принципі по всім фронтовим фреймворкам які використовують схожі технології, а таких немало.
3. Також тут варто розуміти, навіть якщо юзер і заблокує (плагіном чи власною розробкою для браузера) всі подібні запити, він просто не отримає безшовного переходу і все, нічого критичного не станеться. Тому як на мене це Win-Win ситуація : )

3. Ви не даєте вибору користувачеві перебуваючи у впевненості що ваша архітектура це однозначне добро, тому такий плагін — логічний наступний крок у цьому «протистоянні». Пропоную поцікавитися історіями подорожі європейців через Швейцарію або навіть повз неї, коли тарифи ЄС не діють.

Я не казав нічого про однозначне добро)
Ми з вами живемо в світі в якому неможливо уявити веб-сайт який не робить фонові запити (google analytics, Facebook Pixel і цей список можна продовжувати дуже довго) і все це, раз ми вже перейшли в розділ «холіварства» і філософії, робиться заради користувача та покращення його взаємодії з ресурсом. Невже Ви можете уявити сучасний, швидкий, зручний та красивий сайт який не робить фонові запити «без відома» користувача? Я б подивився :)

en.wikipedia.org
basecamp.com
www.sketch.com

Мне кажется, что статическая страница + cdn + cache-control в хедере отлично решает проблему ещё со времен http/1.1.

Хороший абзац, про ddos своего апи. Зачем же тогда пушить в пользователей свои данные и создавать дополнительную нагрузку и гонять данные туда-сюда? Отдавать контент по-запросу выглядит более разумно.

Да и если очень хочется кэшировать данные, почему не сделать один запрос на весь json со всеми данными соседних страниц? 1 запрос на html, второй запрос уже из JS для получения данных. Зачем рекурсивно ходить за каждой страничкой на сервер?)

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

Ви безумовно праві, якщо ми говоримо в площині статичних сторінок, але якщо ми говоримо про більш серйозний контент, який нам потрібно показати юзеру, а саме: реклама, аналітика від гугла, аналітика від фейсбука + купа кастомних метрик які ми трекаємо для себе по різним івентам і так далі (також, не варто забувати, що це все, враховуючи наш випадок, треба динамічно оновлювати, бо юзер ходить по релейтедам і рекомендалкам, поки читає статтю ) і тут, на жаль, Ваша схема вже не відпрацює на достатньому рівні. Ми ж не говоримо про «коня в вакуумі», потрібно розуміти що різні випадки вимагають різних підходів :)

Звісно більш розумно (відавати контент коли юзер вже клікнув на посилання), це ж все таки класична робота лінк-перехід :) Але в нас була ціль і бажання зробити класний UX, тому ми вирішили проблему не тільки рекурсією, а і підтягуванням даних тільки при доскролі юзера, це умовний компроміс. І як показала практика — він того вартий)

Як я вже сказав, у нас не тільки рекурсивні запити, у нас ці запити ще і йдуть лише при необхідності (якщо юзер доскролив до лінки на наступну сторінку), що, як на мене, є красивішим рішенням проблеми ніж відправляти всі запити разом, не звертаючи увагу треба вони чи ні : )

Щойно спробував google.com відкрити без кешу, вийшло більше мегабайту :(

Підписатись на коментарі