Счётчик аналитики, карта кликов, виджет обратного звонка, онлайн-чат — каждый ставится «одной строчкой кода», и каждый тянет за собой десятки и сотни килобайт чужого JavaScript. Всё это выполняется в главном потоке браузера, и PageSpeed считает каждую миллисекунду. На одном сайте мы заблокировали сторонние скрипты для эксперимента — и оценка мобильной главной поднялась с 67 до 90, не тронув ни строчки собственного кода.
Это тридцать вторая часть цикла «PageSpeed без мифов» — о том, как сохранить аналитику и виджеты и не платить за них оценкой.
Где сторонние скрипты бьют по оценке
В отличие от шрифтов и картинок, главная беда здесь не вес в сети, а процессор. Модель PageSpeed замедляет его в четыре раза, а сторонний код обычно большой, разбирается и выполняется целиком. Это попадает в метрику TBT — суммарное время, на которое главный поток был занят длинными задачами.
| Что было на главной | Значение |
|---|---|
| виджет обратного звонка | 641 КБ, сервер виджета отдавал его без сжатия |
| + счётчик аналитики с картой кликов | |
| TBT в отчёте | 2,6–3,2 с |
| собственный код сайта | одна задача на 91 мс |
| оценка со сторонними скриптами / без них | 67 / 90 |
Сделать чужой код быстрее мы не можем. Можем решить, когда он запускается.
Решение: загрузка после первого действия
Сторонние скрипты не нужны для того, чтобы человек увидел страницу. Аналитике достаточно начать работу, когда посетитель проявил себя: прокрутил, коснулся экрана, сдвинул мышь. Виджету звонка — тем более. Поэтому код сторонних сервисов кладём в <template> — браузер его не выполняет — и вставляем на страницу по первому действию посетителя или, если действий нет, через 3,5 секунды после полной загрузки.
<!-- в подвале: код счётчиков и виджетов — как есть, но внутри template -->
<template id="thirdParty">
<script>/* код счётчика аналитики */</script>
<script src="https://widget.example/callback.js" async></script>
</template>
<script>
(() => {
// очередь для вызовов аналитики до её загрузки (формат официального кода Метрики)
window.ym = window.ym || function () { (ym.a = ym.a || []).push(arguments); };
let started = false;
const run = () => {
if (started) return;
started = true;
const tpl = document.getElementById("thirdParty");
tpl.content.querySelectorAll("script").forEach(old => {
const s = document.createElement("script"); // скрипт из template сам не выполнится
[...old.attributes].forEach(a => s.setAttribute(a.name, a.value));
s.text = old.text;
document.body.append(s);
});
};
["pointerdown", "touchstart", "keydown", "scroll", "mousemove"].forEach(e =>
addEventListener(e, run, { once: true, passive: true }));
addEventListener("load", () => setTimeout(run, 3500));
})();
</script>
Три детали, без которых это не работает:
- Скрипт из template сам не выполнится. Если просто перенести содержимое шаблона в страницу, теги
<script>окажутся мёртвыми. Их нужно пересоздать черезdocument.createElement("script"), перенеся атрибуты и текст, — как в коде выше. - Очередь для вызовов до загрузки. Формы и кнопки могут отправлять цели в аналитику раньше, чем она загрузилась. Заглушка
window.ymскладывает вызовы в очередь в том же формате, что и официальный код Метрики, — после загрузки счётчик их подберёт. Для других сервисов — аналогичная заглушка их формата (например,dataLayerдля Google). - Таймер после load, а не после загрузки DOM. 3,5 секунды отсчитываются от полной загрузки страницы: к этому моменту первый экран точно нарисован, и скрипты не попадут в окно первой отрисовки.
Проверьте, что механизм вообще включён. На том самом сайте отложенная загрузка была написана давно и работала… бы. Но шаблон подвала печатал коды счётчиков живыми тегами, а не внутри <template>, — и скрипты грузились сразу, мимо всего механизма. Проверка одной строкой: в исходном коде страницы коды счётчиков должны стоять внутри <template>.
Результат
Мобильная главная того сайта — с 67 до 99. Честности ради: 99 — итог нескольких правок, кроме сторонних скриптов там были ещё шрифты и сдвиги вёрстки из-за картинок без размеров. Но вклад сторонних скриптов виден по эксперименту выше: одни они стоили 23 балла.
По тому же принципу работает и этот сайт: Яндекс Метрика здесь запускается после первого действия посетителя.
Чем за это платим
- Короткие визиты без действий. Если человек открыл страницу и закрыл её через секунду, ничего не тронув, аналитика может его не засчитать. Для большинства сайтов это несколько процентов самых коротких отказов — смиритесь или сравните цифры до и после внедрения.
- Виджеты появляются не сразу. Кнопка обратного звонка выезжает через пару секунд после первого движения. Для посетителя разница незаметна: он всё равно сначала смотрит на страницу.
Это не обман проверки. Посетитель и PageSpeed получают одну и ту же страницу: и у того, и у другого сторонние скрипты стартуют после первого действия или через 3,5 секунды после загрузки. Мы не прячем код от проверки — мы откладываем его для всех.
Другие сторонние ресурсы
- Карты. API карт — сотни килобайт и заметная блокировка потока. Подключайте на странице контактов и только когда блок карты приближается к экрану; подробно — в следующей части цикла.
- Видео с YouTube, VK, Rutube. Встроенный плеер тянет мегабайты ещё до нажатия «плей». Вместо плеера показывайте картинку-превью и подставляйте iframe по клику — об этом тоже отдельная часть.
- Чаты и квизы. Тот же подход, что у виджета звонка: после первого действия.
А async и defer на скриптах не помогают?
Они разрешают браузеру не ждать скрипт при разборе страницы, но скрипт всё равно загрузится и выполнится в первые секунды — внутри окна, которое оценивает PageSpeed, и с той же нагрузкой на процессор. Отложенная загрузка по действию выносит его за это окно.
Может, лучше подключить всё через Google Tag Manager?
Менеджер тегов — ещё один сторонний скрипт, который грузит остальные. Удобно для маркетинга, но для скорости это не решение, а ещё одна задача в главном потоке. Его тоже можно отложить тем же способом.
Сторонние виджеты тормозят сайт?
Пришлите адрес — посчитаю, сколько баллов стоит каждый сервис, и настрою отложенную загрузку так, чтобы аналитика продолжала работать.
// загружаю комментарии…