Оптимизация 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: какие функции были вызваны и сколько времени заняла каждая.

Ключевые метрики в трейсе:

  • Total blocking time — сумма (duration - 50ms) по всем Long Tasks
  • Scripting в Summary — доля времени на выполнение JS
  • Rendering — перерисовка DOM
  • Painting — пиксельная отрисовка

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

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // Long Task обнаружена
    console.log({
      duration: entry.duration,
      startTime: entry.startTime,
      // attribution — в каком скрипте/iframe
      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()

Самый прямой способ устранить Long Task — разбить её на части и между частями отдавать управление браузеру.

Старый способ через 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);

    // Yield: браузер получает шанс обработать ввод
    if (i + CHUNK_SIZE < items.length) {
      await scheduler.yield();
    }
  }
}

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

function yieldToMain() {
  // scheduler.yield() если есть, иначе MessageChannel для минимальной задержки
  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) => {
        // сложная многокритериальная сортировка
        return complexSort(a, b);
      });
      break;

    case 'PARSE_CSV':
      result = parseCSV(payload);
      break;

    case 'FILTER_CATALOG':
      result = filterWithComplexRules(payload.items, payload.filters);
      break;
  }

  self.postMessage({ type: type + '_DONE', result });
};
// main.js
class WorkerPool {
  constructor(workerScript, poolSize = navigator.hardwareConcurrency || 4) {
    this.workers = Array.from({ length: poolSize }, () => new Worker(workerScript));
    this.queue = [];
    this.available = [...this.workers];
  }

  run(type, payload) {
    return new Promise((resolve, reject) => {
      const task = { type, payload, resolve, reject };

      if (this.available.length > 0) {
        this._dispatch(task);
      } else {
        this.queue.push(task);
      }
    });
  }

  _dispatch({ type, payload, resolve, reject }, worker = this.available.pop()) {
    worker.onmessage = ({ data }) => {
      resolve(data.result);
      this.available.push(worker);
      if (this.queue.length > 0) {
        this._dispatch(this.queue.shift());
      }
    };
    worker.onerror = reject;
    worker.postMessage({ type, payload });
  }
}

const pool = new WorkerPool('/heavy-worker.js', 2);

// Использование
const sortedProducts = await pool.run('SORT_PRODUCTS', products);

Паттерн 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 с задержкой
    setTimeout(() => work.forEach(t => t()), 1);
  }
}

// Использование
scheduleNonCritical([
  () => preloadNextPageData(),
  () => sendAnalyticsBatch(),
  () => syncLocalStorage(),
]);

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

React 18 добавил механизм явного разграничения срочных и несрочных обновлений.

startTransition для некритичных обновлений состояния:

import { startTransition, useState } from 'react';

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 может быть тяжёлым, но не блокирует ввод */}
      <SearchResults query={searchQuery} />
    </>
  );
}

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

import { useDeferredValue, memo } from 'react';

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,                    // рендерим 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,
    element: interactionTarget,
    processingTime: processingEnd - processingStart,
  });
});

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

Сроки

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