Оптимізація Long Tasks для покращення відзивчивості сайту

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

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Оптимізація Long Tasks для покращення відзивчивості сайту
Складна
~3-5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Оптимізація Long Tasks для поліпшення відзивчивості сайту

Long Task — будь-яка задача в main thread браузера, виконання якої займає більше 50 мілісекунд. Поки виконується така задача, браузер не може обробити користувацький ввід: клік, скролл, нажатие клавіші. Користувач бачить "замерзлий" інтерфейс. 50 мс — це поріг сприйняття: затримки до 50 мс не відчуваються, усе що вище — вже "тупить".

Long Tasks — першопричина поганих значень TTI, TBT та INP. Зрозуміти, що саме їх викликає — це не швидка робота. Тут нему одного магічного трюку.

Діагностика: профілірування в Chrome DevTools

Відкриваємо DevTools → Performance → починаємо запис → імітуємо завантаження або взаємодію → зупиняємо.

На треку Main бачимо червоні трикутники на задачах довше 50 мс. Кліакємо по задачі — в нижній панелі з'являється call tree: які функції були викликані та скільки часу займала кожна.

Для production-профілювання без DevTools:

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log({
      duration: entry.duration,
      startTime: entry.startTime,
      attributions: entry.attribution?.map(a => ({
        containerType: a.containerType,
        containerSrc: a.containerSrc,
        name: a.name,
      })),
    });

    navigator.sendBeacon('/api/longtasks', JSON.stringify({
      duration: entry.duration,
      startTime: entry.startTime,
      url: location.href,
      userAgent: navigator.userAgent,
    }));
  }
});

observer.observe({ type: 'longtask', buffered: true });

Паттерн 1: розбивка задачі на чанки з scheduler.yield()

Найпряміший спосіб — розбити задачу на частини та між частинами віддати управління браузеру.

Старий спосіб через setTimeout(0):

function processLargeArray(items) {
  const CHUNK_SIZE = 100;
  let index = 0;

  function processChunk() {
    const end = Math.min(index + CHUNK_SIZE, items.length);
    for (let i = index; i < end; i++) {
      processItem(items[i]);
    }
    index = end;
    if (index < items.length) {
      setTimeout(processChunk, 0);
    }
  }

  processChunk();
}

Сучасний спосіб через scheduler.yield() (Chrome 115+):

async function processLargeArrayModern(items) {
  const CHUNK_SIZE = 100;
  for (let i = 0; i < items.length; i += CHUNK_SIZE) {
    const chunk = items.slice(i, i + CHUNK_SIZE);
    chunk.forEach(processItem);
    if (i + CHUNK_SIZE < items.length) {
      await scheduler.yield();
    }
  }
}

Полісфіл для браузерів без scheduler.yield():

function yieldToMain() {
  if ('scheduler' in self && 'yield' in scheduler) {
    return scheduler.yield();
  }
  return new Promise(resolve => {
    const channel = new MessageChannel();
    channel.port1.onmessage = resolve;
    channel.port2.postMessage(undefined);
  });
}

Паттерн 2: Web Workers для CPU-intensive вичислень

Вичисления, які не роблять з DOM, виносимо в Worker. Типічні кандидати: парсинг великих JSON, криптографія, складні фільтри, обробка файлів.

// heavy-worker.js
self.onmessage = function({ data: { type, payload } }) {
  let result;
  switch (type) {
    case 'SORT_PRODUCTS':
      result = payload.sort((a, b) => complexSort(a, b));
      break;
    case 'PARSE_CSV':
      result = parseCSV(payload);
      break;
  }
  self.postMessage({ type: type + '_DONE', result });
};

// main.js
const worker = new Worker('/heavy-worker.js');
worker.postMessage({ data: largeArray, operation: 'sort' });
worker.onmessage = (e) => setTableData(e.data);

Паттерн 3: requestIdleCallback для некритичних задач

Аналітика, передзавантаження, збереження стану — все некритичне відкладаємо до простоя браузера:

function scheduleNonCritical(work) {
  if ('requestIdleCallback' in window) {
    requestIdleCallback((deadline) => {
      while (deadline.timeRemaining() > 0 && work.length > 0) {
        const task = work.shift();
        task();
      }
      if (work.length > 0) {
        scheduleNonCritical(work);
      }
    }, { timeout: 2000 });
  } else {
    setTimeout(() => work.forEach(t => t()), 1);
  }
}

Паттерн 4: React 18 — startTransition та useDeferredValue

React 18 додав механізм явного розрізнення срочних та несрочних оновлень.

startTransition для несрочних оновлень:

function SearchPage() {
  const [inputValue, setInputValue] = useState('');
  const [searchQuery, setSearchQuery] = useState('');

  function handleInput(e) {
    const value = e.target.value;
    setInputValue(value);  // Срочне
    startTransition(() => {
      setSearchQuery(value);  // Несрочне
    });
  }

  return (
    <>
      <input value={inputValue} onChange={handleInput} />
      <SearchResults query={searchQuery} />
    </>
  );
}

useDeferredValue для списків:

const MemoizedList = memo(function ExpensiveList({ items, filter }) {
  const filtered = items.filter(item => matchesComplexFilter(item, filter));
  return <div>{filtered.map(item => <Item key={item.id} {...item} />)}</div>;
});

function FilteredCatalog({ products, filter }) {
  const deferredFilter = useDeferredValue(filter);
  const isStale = filter !== deferredFilter;

  return (
    <div style={{ opacity: isStale ? 0.7 : 1 }}>
      <MemoizedList items={products} filter={deferredFilter} />
    </div>
  );
}

Паттерн 5: віртуалізація довгих списків

Рендеринг тисяч DOM-елементів — одна велика Long Task. Віртуалізація рендерить тільки видимі елементи.

З @tanstack/react-virtual:

import { useVirtualizer } from '@tanstack/react-virtual';

function VirtualList({ items }) {
  const parentRef = useRef(null);
  const virtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 72,
    overscan: 5,
  });

  return (
    <div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
      <div style={{ height: virtualizer.getTotalSize() }}>
        {virtualizer.getVirtualItems().map(virtualItem => (
          <div
            key={virtualItem.key}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              transform: `translateY(${virtualItem.start}px)`,
            }}
          >
            <ListItem item={items[virtualItem.index]} />
          </div>
        ))}
      </div>
    </div>
  );
}

Вимірювання результату

До та після лабораторних умов (Lighthouse, 4× CPU throttle, 3G) та польових (INP через web-vitals):

import { onINP } from 'web-vitals/attribution';

onINP(({ value, attribution }) => {
  const { interactionType, interactionTarget, processingStart, processingEnd } = attribution;
  console.log({
    inp: value,
    interaction: interactionType,
    processingTime: processingEnd - processingStart,
  });
});

Типовий результат після повного цикла оптимізації: TBT знижується з 1–3 секунд до < 300 мс, INP поліпшується з 400–600 мс до < 200 мс.

Терміни

Діагностика Long Tasks через DevTools + профілірування production — 2–3 робочих дні. Повний цикл: code splitting, Web Workers, React transitions, віртуалізація — 2–4 тижні для складного SPA. Простий сайти з переважно серверним рендерингом — 3–7 робочих днів.