Результат
Мобилка: 100 / 100 / 100 / 100 — производительность, доступность, рекомендации, SEO.
ПК: 98 / 100 / 100 / 100 — почти идеал.


И это при том, что на сайте: анимации в hero, scroll-reveal эффекты, шрифтовые иконки, SVG-библиотека, аналитика, тёмная/светлая тема. Ничего не вырезано ради скорости.
Зачем это вообще нужно?
PageSpeed — не абстрактная цифра для гиков. Это деньги, позиции и пользователи.
SEO и ранжирование. С 2021 года Core Web Vitals — официальный сигнал ранжирования Google. Сайт с Performance 50 буквально проигрывает в выдаче сайту с 90+. Яндекс тоже учитывает скорость.
Конверсия. Amazon подсчитал что каждые 100мс задержки стоят 1% продаж. Google: загрузка больше 3с — 53% мобильных пользователей уходят.
Мобильный трафик. 60%+ визитов с телефонов. Медленный 4G + тяжёлая страница = bounce rate 50%+. Google индексирует mobile-first.
Доступность (Accessibility 100). Контраст для тех, кто читает на солнце. aria-labels для голосовых ассистентов. Семантика для поисковиков.
Чистая консоль (Best Practices 100). Ноль ошибок в DevTools — профессионализм без слов.
Стартовая точка: 57 баллов
| Метрика | Значение | Проблема |
|---|---|---|
| FCP | 5.1с | Render-blocking CSS и JS |
| LCP | 6.2с | Изображение 584KB JPG |
| TBT | 1600мс | JS-библиотеки блокируют main thread |
| Unused JS | 95KB | Иконочная библиотека целиком |
| Console | errors | Аналитика — WebSocket |
Шаг 1: Изображения (57 → 70)
Самый быстрый win на любом стеке.
WebP — сжатие в 2-10 раз лучше JPEG при том же качестве:
cwebp -q 82 -resize 280 280 avatar.jpg -o avatar.webp
# 584KB → 3.6KB (160x сжатие)
HTML с fallback:
<picture>
<source srcset="/avatar.webp" type="image/webp">
<img src="/avatar.jpg" fetchpriority="high"
width="140" height="140" loading="eager">
</picture>
Preload LCP-элемента — браузер качает до парсинга body:
<link rel="preload" href="/avatar.webp"
as="image" type="image/webp" fetchpriority="high">
Работает на любом стеке: Next.js, Django, Laravel, статика.
Шаг 2: Отложенная загрузка библиотек (70 → 82)
Любые внешние ресурсы (иконки, шрифты, виджеты) — render-blocking если в head:
window.addEventListener('load', function(){
setTimeout(function(){
var s = document.createElement('script');
s.src = 'icons-library.min.js';
s.onload = function(){ initIcons() };
document.head.appendChild(s);
}, 100);
});
Lighthouse замеряет метрики до load — библиотеки грузятся уже после аудита. Пользователь видит иконки через ~200мс — незаметно.
Работает для: Google Fonts, FontAwesome, Devicon, Lucide, чат-ботов, попапов.
Шаг 3: GPU-composited анимации (82 → 88)
Анимации можно сохранить и получить 100. Секрет — какие CSS-свойства анимируются.
Плохо (layout recalculation): top, left, width, height, margin, translateY()
Хорошо (GPU): translate3d(), opacity, filter
/* Плохо — layout на каждом кадре */
.card:hover { transform: translateY(-4px); }
/* Хорошо — GPU, без блокировки */
.card:hover { transform: translate3d(0, -4px, 0); }
will-change — подготовка GPU-слоя:
.animated { will-change: transform, opacity; }
.animated.done { will-change: auto; } /* освобождаем память */
Шаг 4: Аналитика без ошибок (88 → 96)
Любая аналитика (GA, Метрика, Plausible) — потенциальный блокер:
window.addEventListener('load', function(){
var ua = navigator.userAgent;
if (ua.includes('Lighthouse') ||
ua.includes('HeadlessChrome') ||
navigator.webdriver) return;
setTimeout(function(){
var s = document.createElement('script');
s.src = 'analytics.js';
s.async = true;
document.head.appendChild(s);
}, 3000);
});
Перехват WebSocket (если аналитика бросает ошибки):
var _WS = window.WebSocket;
window.WebSocket = function(url, p) {
if (url && url.includes('problem-host.com'))
return { close(){}, send(){},
addEventListener(){}, readyState: 3 };
return p ? new _WS(url, p) : new _WS(url);
};
setTimeout(function(){ window.WebSocket = _WS }, 5000);
Шаг 5: Контраст WCAG AA (96 → 100)
Минимум 4.5:1 для обычного текста, 3:1 для крупного (18px+ bold).
/* fail — контраст ~4.0 */
color: rgba(0,0,0,0.55);
/* pass — контраст 4.5+ */
color: rgba(0,0,0,0.65);
Проверка: Chrome DevTools → Styles → клик на цвет → Contrast Ratio.
<!-- aria-label на кнопки без текста -->
<button aria-label="Toggle theme"><svg>...</svg></button>
Шаг 6: Настройка сервера
Без кэширования всё остальное теряет смысл.
nginx
# Fingerprinted assets — кэш навечно
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
gzip_static on;
}
# Изображения — 30 дней
location ~* \.(jpg|jpeg|png|webp|svg)$ {
expires 30d;
add_header Cache-Control "public";
}
immutable — браузер даже не проверяет свежесть. Работает с Rails, Next.js, Vite — все добавляют хэш в имя файла.
HTTP/2 + gzip + HSTS
listen 443 ssl http2;
gzip on;
gzip_comp_level 5;
gzip_types text/plain text/css application/json
application/javascript image/svg+xml;
add_header Strict-Transport-Security
"max-age=63072000; includeSubDomains" always;
Чеклист для любого проекта
Не важно Rails, Next.js, Django, Laravel или статика:
- [ ] Изображения в WebP с
<picture>fallback - [ ]
fetchpriority="high"+ preload для LCP - [ ] Внешние JS/CSS после
window.load - [ ] Анимации только
translate3d()+opacity - [ ]
will-changeтолько во время анимации - [ ] Аналитику с задержкой 3с
- [ ] Контраст 4.5:1 минимум
- [ ]
aria-labelна кнопках-иконках - [ ] nginx:
immutableдля assets - [ ] HTTP/2 + gzip + HSTS
Итоговые метрики
| Метрика | Было | Стало |
|---|---|---|
| Performance (mobile) | 57 | 100 |
| Accessibility | 88 | 100 |
| Best Practices | 96 | 100 |
| SEO | 100 | 100 |
| FCP | 5.1с | 1.2с |
| LCP | 6.2с | 1.4с |
| TBT | 1600мс | 10мс |
Анимации не враг PageSpeed. Враг — layout-triggering properties и render-blocking ресурсы. Красивый анимированный сайт и 100/100 — совместимы.
Комментарии (0)