П’ять шаблонів завантаження даних для підвищення швидкодії сайтів

підвищення швидкодії сайтів

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

Коли справа доходить до швидкодії, обмежувати себе не варто. У мережі існують мільйони сайтів, і ви перебуваєте у тісному суперництві з усіма учасниками пошукової видачі Google. Дослідження показало, що користувачі схильні залишати сайти, що завантажуються довше трьох секунд. Три секунди – це небагато. І хоча сьогодні багато ресурсів завантажуються менше, ніж за секунду, жодне рішення не універсальне, і перший запит користувача може стати для вашої програми визначальним.

Сучасні фронтенд-додатки стають дедалі більше. Не дивно, що промисловість серйозно спантеличилася питанням оптимізації. При використанні фреймворків ми отримуємо неадекватні розміри програми, які можуть піти йому не на користь, а на шкоду. Будь-який необов’язковий фрагмент JavaScript, який ви збираєте та відправляєте у продакшен, у результаті лише збільшить кількість коду, який клієнту потрібно завантажити та обробити. Тут чергове правило каже — що менше, то краще.

Шаблони завантаження даних є найважливішою частиною вашої програми, оскільки визначають, які його частини є безпосередньо доступними для відвідувачів. Не створюйте сайт, який гальмуватиме у клієнтів через необхідність завантажити зображення розміром 5МБ на головній сторінці. Намагайтеся краще зрозуміти принципи. Вам потрібно добре розібратися у водоспаді завантаження ресурсів.

Перебір зі спіннерами завантаження та водоспад даних

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

Наочно подивитися це можна, відкривши браузер і зазирнувши у вкладку Network інструментів розробника на прикладі Medium.

Домашня сторінка Medium

Тут у нас два важливі компоненти:

  1. Графік показує тимчасову лінію кожного запрошеного і завантаженого файла. Тут ви бачите, які файли йдуть першими, і можете простежити кожен послідовний запит до моменту, коли завантаження файлу займає тривалий час. Далі цей файл можна проінспектувати та з’ясувати, чи є можливість його оптимізувати.
  2. Внизу сторінки можна перевірити, скільки КБ ресурсів споживає ваш клієнт. Важливо, скільки даних клієнту необхідно завантажувати. При першій спробі цю інформацію можна використовувати як бенчмарк для подальших оптимізації.

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

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

Але хіба очікування даних не є зрозумілим?

Так, але їхнє завантаження можна прискорити.

Loading

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

  • даних від API бекенда;
  • створення макета згідно з цими даними.

Спочатку ви здійснюєте асинхронний виклик до API, потім отримуєте URL-адресу ресурсу в CDN і тільки після цього можете почати створювати макет на клієнтській стороні. Це чимало роботи, коли потрібно показати ваше обличчя, ім’я, статус та пости Instagram відразу.

П’ять важливих шаблонів завантаження даних

Розробляти програмне забезпечення стає все простіше, оскільки фреймворки на кшталт React, Vue або Angular дозволяють з легкістю створювати найпростіші програми. Однак використання цих громіздких інструментів, заповнених купою всіляких функцій, які ви навіть не використовуєте, не варто розглядати як оптимальне рішення.

Ви тут щоб оптимізувати. Пам’ятайте, що менше, тим краще.

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

Рендеринг на стороні сервера та Jamstack

У сучасних JS-фреймворках відображення сторінок найчастіше реалізується на клієнтській стороні (CSR). Браузер отримує у вигляді корисного навантаження JS-бандл та статичний HTML, після чого малює DOM, а також для реактивності додає слухачів та активатори подій. Коли така програма відображається в DOM, сторінка блокується до завершення цього процесу. Рендеринг робить програму реактивною. Для його виконання необхідно зробити ще один виклик API до сервера та вийняти всі необхідні дані.

У свою чергу, малювання на стороні сервера (SSR) передбачає передачу програмою простого HTML клієнту. SSR можна розділити на два типи: з гідратацією та без гідратації. SSR – це стара техніка, яка використовується такими фреймворками, як WordPress, Ruby on Rails та ASP.NET. Основне її завдання – надати користувачеві статичний HTML із необхідними даними. На відміну від CSR, SSR не вимагає додаткового виклику API до сервера, оскільки саме сервер генерує шаблон HTML і завантажує дані.

У більш сучасних рішеннях на зразок Next.js використовується гідратація, коли статичний HTML гідратується на клієнтській стороні за допомогою JS. Можна порівняти це із заварюванням швидкорозчинної кави: кавовий порошок – це HTML, а вода – це JS. При змішуванні порошку з водою ви, очевидно, отримаєте каву.

Але що таке Jamstack? Jamstack аналогічний SSR, оскільки клієнт отримує простий HTML. Але під час SSR клієнт отримує HTML з сервера, а у випадку з Jamstack програми передають заздалегідь згенерований HTML прямо з CDN. У зв’язку з цим вони зазвичай завантажуються швидше, але тут розробникам складніше створювати динамічний контент. Такі програми рахунок попередньої генерації HTML хороші клієнта. Однак при використанні великого обсягу JS-коду на стороні клієнта стає все складніше виправдати використання Jamstack замість СSR.

І SSR, і Jamstack мають особливості. Спільне з-поміж них те, що вони не навантажують клієнта рендерингом всієї сторінки, використовуючи JS.

Jamstack у порівнянні з SSR та CSR

Коли ви оптимізуєте SEO свого сайту, рекомендується використовувати SSR або Jamstack, оскільки порівняно з CSR ці техніки повертають HTML-файли, зручні для перегляду пошуковими роботами. Пошуковики, звичайно, також можуть переглядати та компілювати JS-файли для CSR. Хоча відображення кожного JS-файлу в додатку з CSR може бути витратним за часом і знижувати ефективність SEO вашого сайту.

SSR і Jamstack дуже популярні, і все більше проектів переходять на кадри для SSR на кшталт Next.js і Nuxt.js, відмовляючись від їх ванільних CSR-аналогів – React і Vue. Головна причина в тому, що SSR-фреймворки забезпечують більшу гнучкість щодо SEO. У Next.js є цілий розділ, орієнтований на SEO оптимізації.

Додаток з SSR зазвичай містить шаблонизатори, які впроваджують змінні HTML під час його передачі клієнту. Наприклад, у Next.js можна завантажити список студентів, написавши:

export default function Home({ studentList }) {
  return (
    <Layout home>
        <ul>
          {studentList.map(({ id, name, age }) => (
            <li key={id}>
              {name}
              <br />
              {age}
            </li>
          ))}
        </ul>
    </Layout>
  );
}

Jamstack популярний для сайтів документації, які зазвичай компілюють код HTML-файли і розміщують їх в CDN. Файли Jamstack зазвичай до компіляції в HTML використовують розмітку Markdown. Ось приклад:

---
author: Agustinus Theodorus
title: 'Title'
description: Description
---
Hello World

Активне кешування пам’яті

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

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

Але як робити правильний вибір між кешем Redis (серверним) та кешем браузера (локальним)? Обидва ці варіанти можна використовувати одночасно, але кожен із них буде слугувати своїй меті.

Схеми кешування

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

Загалом, якщо ви хочете підвищити швидкодію програми, то можете використовувати для прискорення API серверний кеш. Якщо ж ви хочете зберігати стан програми, варто вдаватися до локального. І хоча локальний кеш може здатися марним, він допомагає скоротити кількість викликів API до бекенду, зберігаючи стан, який рідко змінюється. Однак локальний кеш працює ефективніше при поєднанні з «живими» даними.

Генерація подій даних

Встановити з’єднання в режимі реального часу між фронтендом та бекендом можна через WebSockets. WebSockets – це механізм двосторонньої комунікації, що спирається на події.

Типова архітектура Websocket

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

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

Архітектура генерації подій

У чистій реалізації WebSocket, як і раніше, є безліч недоліків. Використання цієї технології замість стандартних HTTP-дзвінків повністю змінить поведінку програми. Навіть одна невелика проблема з підключенням зможе вплинути на весь досвід користувача. Наприклад, WebSocket неспроможна забезпечити швидкодію реального часу – коли потрібно зв’язатися з базою даних, використовується запит GET. Щоб зробити WebSocket раціональним та доцільним вибором у бекенді необхідно усунути ряд слабких місць, що перешкоджають ефективній роботі в режимі реального часу.

Для реалізації таких можливостей потрібний основний архітектурний шаблон, наприклад, «генерація подій» який можна використовувати для створення надійних real-time додатків. І хоча він не гарантує загальне підвищення швидкодії програми, цей шаблон явно покращить досвід користувача, надавши клієнтам можливість використовувати UI в режимі реального часу.

У сучасному JS доступні для використання провайдери WebSocket. Клас WebSocket відкриває з’єднання з віддаленим сервером і дозволяє прослуховувати, коли WebSocket створює з’єднання, закриває його, повертає помилку або повертає подію.

const ws = new WebSocket('ws://localhost');ws.addEventListener('message', (event) => {
    console.log('Message from server ', event.data);
});

Бажаєте реагувати на події сервера? Додайте функцію addEventListener та вставте зворотний дзвінок, який вона використовуватиме.

ws.send('Hello World');

Бажаєте надсилати повідомлення? І тут WebSocket має рішення. Надсилання повідомлень із сервера можна реалізувати за допомогою функції send. Причому це не складніше реалізації Hello World. Приклади взято з документації MDN .

Попереднє та відкладене завантаження даних

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

Попереднє завантаження

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

<link rel="prefetch" href="https://example.com/example.html">

URL для попереднього завантаження встановлюються в атрибуті relHTML-елемента link. Однак дана техніка має як плюси, так і мінус.

Плюси:

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

Мінус:

  1. Цей прийом можна використовувати для завантаження трекерів, що ставить безпеку користувача під загрозу.

Відкладене завантаження

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

Відкладене завантаження

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

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

Resumability

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

Головна ідея Resumability полягає у передачі стану програми від сервера клієнту у вигляді серіалізації. Замість завантаження всього коду (HTML, JS) з подальшим його гідратуванням у фронтенді при використанні Resumability ми розбиваємо парсингу JS-коду на стадії та відправляємо їх клієнту у вигляді HTML.

Порівняння Resumability та гідатації. Зображення взято з Qwik

Завантаження сторінок буде блискавичним, оскільки клієнт замість перезавантаження чогось десеріалізуватиме стан, впроваджений в HTML. Resumability є досить чужорідною концепцією і багатьох проектів здасться зовсім незвичним прийомом. Придумав цей шаблон творець Qwik, Міско Хевері.

Qwik– це JS-фреймворк, що внутрішньо працює за принципом Resumability, який у нього закладався спочатку. Якщо ж говорити про такі фреймворки, як React і Vue,  то вони ніколи не зможуть задіяти техніку Resumability, не пожертвувавши зворотною сумісністю. Причина в тому, що компонент відкладеного завантаження Qwik працює асинхронно на відміну від більшості синхронно влаштованих фреймворків JavaScript.

Завдання Qwik – завантажувати якнайменше JS-коду. Справа в тому, що робити це відкладено досить важко, а в деяких випадках навіть неможливо. Чим менше вам потрібно, тим краще. Resumability дозволяє розробникам детально налаштувати відкладене завантаження та скоротити споживання пам’яті мобільними програмами, оптимізуючи під них сайт.

Використання Qwik у певному сенсі нагадує React – зокрема вони схожий синтаксис. Ось фрагмент коду Qwik. Основа програми буде представлена ​​у формі HTML:

import { App } from './app';export const Root = () => {
  return (
    <html>
      <head>
        <title>Hello Qwik</title>
      </head>
      <body>
        <App />
      </body>
    </html>
  );
};

Тут є залежність від App компонента Qwik для відкладеного завантаження:

import { component$ } from '@builder.io/qwik';
export const App = component$(() => {
  return <p>Hello Qwik</p>;
});

Qwik і React також мають схожість на рівні компонентів, а ось у серверній частині вони вже відрізняються.

import { renderToString, RenderOptions } from '@builder.io/qwik/server';
import { Root } from './root';export default function (opts: RenderOptions) {
  return renderToString(<Root />, opts);
}

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

Узагальнення

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

Однак, якщо ви працюєте над великими корпоративними програмами, то тут спосіб оптимізації вже знайти складніше. Шаблони завантаження даних є одним з можливих варіантів підвищення швидкодії ваших додатків. У цій статті ми розібрали їх п’ять:

  1. Відображення на стороні сервера (SSR) та Jamstack.
  2. Активне кешування пам’яті.
  3. Генерація подій даних.
  4. Попереднє та відкладене завантаження.
  5. Resumability.

Кожен із них ефективний на своєму місці.

SSR та Jamstack зазвичай добре підходять для додатків, що вимагають меншого керування станом на стороні клієнта. З появою сучасних фреймворків на зразок React все більше людей познайомилися з патерном відмальовки на клієнтській стороні (CSR), але в результаті спільнота все ж таки повертається до SSR. Техніка SSR використовується у старих MVC фреймворках, де за допомогою шаблонізаторів на основі даних бекенда генерується HTML. Jamstack – це ще більш стара ілюстрація початкового веб-середовища, в якому використовувався лише HTML.

Активне кешування пам’яті дозволяє швидше завантажувати дані з API. Ця техніка вирішує важливу проблему завантаження даних шляхом їх кешування або на віддаленому сервері (Redis) або локально в браузері. Крім цього, вона використовується в шаблоні попереднього завантаження даних.

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

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

Відкладене завантаження скорочує обсяг ресурсів, необхідних для завантаження при першому натисканні. Вам потрібні лише ті, які ви бачите безпосередньо після завантаження сторінки. І цю техніку додатково розширює шаблон Resumability. Він має на увазі відкладене завантаження JS-компонентів шляхом їх часткового відтворення на сервері з подальшою серіалізацією стану для завершення розпочатого відтворення вже на стороні клієнта за допомогою HTML.

Подальші кроки

Вироблення навичок оптимізації додатків є безперервним процесом. Вам потрібно продумувати все наперед у проектах, які реалізуєте день у день. Шаблони завантаження даних – це лише один із способів для підвищення швидкодії.

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

Переклад статті “Five Data-Loading Patterns To Improve Web Performance