Производительность веб-приложения: где тормоза, как их найти и как исправить
Веб-приложение, которое грузится 5 секунд — это не просто цифра в отчетах. Это пользователи, которые уходят, снижение конверсии и дополнительные расходы на рекламу. В реальности производительность — это не отдельная задача, а система из метрик, инструментов и процессов, которые должны работать вместе. Если вы оптимизируете только фронтенд, но бэкенд возвращает данные с задержкой, или наоборот — красиво рендерится, но сервер валится под нагрузкой, пользователь всё равно уйдет.
В этой статье разберем:
- Что именно мешает приложению быть быстрым (и как это проверить).
- Как оптимизировать загрузку (не только CSS/JS, но и сетевые запросы, кэширование, CDN).
- Как ускорить рендер (Critical CSS, defer, lazy-loading, виртуальные списки).
- Что ломает производительность в продакшене (и как этого избежать).
- Как внедрить оптимизацию в процесс разработки (без лишних докеров и сложных конфигов).
Проблема: почему приложение тормозит (и как это проверить)
Обычно проблемы с производительностью проявляются в трех местах:
- Загрузка ресурсов (HTML, CSS, JS, шрифты, изображения).
- Рендеринг страницы (время до первого контента, взаимодействия).
- Взаимодействие (отклик на клики, прокрутку, анимации).
Если вы не измеряете эти метрики — вы слепы. Вот минимальный набор инструментов для диагностики:
1. Lighthouse (Chrome DevTools)
Запустите в браузере F12 → Lighthouse → выберите "Performance". Получите отчет с оценкой и конкретными проблемами:
# Запуск через CLI (нужен Node.js)
npx lighthouse https://ваш-сайт.ru --chrome-flags="--headless" --output=html --output-path=report.html
Что искать в отчете:
First Contentful Paint (FCP)> 1.8с — пользователь видит пустоту.Time to Interactive (TTI)> 3.8с — приложение не откликается на действия.Total Blocking Time (TBT)> 200мс — тормозит при прокрутке/кликах.
2. WebPageTest
Бесплатный сервис с геолокацией тестов. Запустите:
# Пример команды для теста из США
webpagetest --location=US:Seattle --test https://ваш-сайт.ru
Ключевые метрики:
- Start Render (время до первого визуального контента).
- Fully Loaded (время до полной загрузки).
- Filmstrip (визуальная анимация загрузки — покажет, где "подвисания").
3. Real User Monitoring (RUM)
Если у вас уже есть трафик — используйте:
- Google Analytics 4 (встроенные метрики
FCP,LCP). - Sentry Performance (для отслеживания JS-тормозов).
- New Relic (для комплексного мониторинга фронтенда и бэкенда).
Пример конфига для Sentry:
// sentry.init.js
Sentry.init({
dsn: "ВАШ_DSN",
integrations: [
new Sentry.BrowserTracing({
tracingOrigins: ["https://ваш-сайт.ru"],
}),
new Sentry.Replay(),
],
tracesSampleRate: 1.0,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
});
Практика: как ускорить загрузку (и не ломать кэш)
Самая частая ошибка — оптимизировать фронтенд, но не думать о сетевых запросах, кэше и серверной части. Вот проверенные методы:
1. Оптимизация ресурсов
Изображения: Используйте
WebPвместо JPEG/PNG и сервисную оптимизацию (например, Cloudinary или ImageKit).<!-- Пример с lazy-loading и WebP --> <img src="placeholder.jpg" data-src="https://res.cloudinary.com/demo/image/upload/w_800,q_auto:good,f_auto/photo.jpg" loading="lazy" width="800" height="600" alt="Описание" >Инструмент для проверки: Squoosh — сжимает изображения без потери качества.
Шрифты: Загружайте только нужные символы и используйте
font-display: swap:@font-face { font-family: 'Roboto'; src: url('roboto.woff2') format('woff2'); font-display: swap; }CSS/JS: Используйте
deferдля JS иpreloadдля критических ресурсов:<!-- Critical CSS в head --> <link rel="preload" href="critical.css" as="style" onload="this.rel='stylesheet'"> <!-- Некритический JS с defer --> <script src="analytics.js" defer></script>
2. Кэширование и CDN
- Настройте
Cache-Controlна сервере:# Пример конфига для Nginx location ~* \.(jpg|jpeg|png|gif|webp|svg|ico|woff2?|ttf|eot|otf)$ { expires 1y; add_header Cache-Control "public, immutable"; access_log off; } - Используйте CDN (Cloudflare, Fastly, AWS CloudFront) для статики. Пример конфига Cloudflare:
{ "rules": [ { "action": "cacheLevel", "value": "cacheEverything", "conditions": [ { "operator": "matches", "key": "url", "value": "*.{jpg,png,css,js,webp}" } ] } ] }
3. HTTP/2 и HTTP/3
- Включите HTTP/2 на сервере (поддерживает мультиплексирование запросов):
listen 443 ssl http2; - Для HTTP/3 используйте QUIC (например, через Cloudflare или Caddy):
# Пример конфига Caddy ваш-сайт.ru { tls { alpn http/3 } }
Примеры: как оптимизировать рендер
Рендер — это когда браузер начинает отрисовывать страницу. Если он тормозит, пользователь видит "серый экран смерти". Вот как это исправить:
1. Critical CSS
Выделите CSS, необходимый для отображения выше складки (above-the-fold). Инструменты:
Пример встроенного Critical CSS:
<style>
/* Сгенерированный Critical CSS */
body { font-family: Roboto; }
.hero { background: #fff; }
/* ... */
</style>
<link rel="preload" href="non-critical.css" as="style" onload="this.rel='stylesheet'">
2. Lazy-loading и виртуальные списки
- Для изображений и iframe:
<img src="image.jpg" loading="lazy" alt="..."> - Для длинных списков (например, таблиц данных) используйте виртуальную прокрутку:
// Пример с react-window import { FixedSizeList as List } from 'react-window'; const Row = ({ index, style }) => ( <div style={style}>Элемент {index}</div> ); const VirtualizedList = () => ( <List height={500} itemCount={10000} itemSize={50} width="100%" > {Row} </List> );
3. Code Splitting и Dynamic Imports
Разбивайте код на чанки и загружайте их по требованию:
// В React
import React, { Suspense, lazy } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function App() {
return (
<Suspense fallback={<div>Загрузка...</div>}>
<HeavyComponent />
</Suspense>
);
}
Ошибки: что убивает производительность (и как этого избежать)
| Ошибка | Причина | Как исправить |
|---|---|---|
| Блокирующий JavaScript | Скрипты загружаются в <head> |
Использовать defer или async |
| Неоптимизированные изображения | JPEG/PNG без сжатия, большие размеры | Конвертировать в WebP, использовать CDN |
| Избыточные ререндеры | Частые изменения DOM/state | Использовать React.memo, useMemo |
| Без кэша для статики | Cache-Control: no-cache |
Настроить кэш на год для статики |
| Слишком много HTTP-запросов | Необдуманные API-вызовы | Объединять запросы, использовать GraphQL |
| Блокирующий CSS | Внешние стили без preload |
Выносить критический CSS в <head> |
| Неиспользуемые полифилы | Подключение старых библиотек | Удалять ненужные зависимости (npm prune) |
Вывод: как внедрить оптимизацию в процесс
Производительность — это не разовая задача, а часть DevOps-процесса. Вот как сделать это рабочим:
Автоматизируйте тесты Добавьте Lighthouse в CI/CD (например, GitHub Actions):
# .github/workflows/performance.yml name: Performance Check on: push jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - run: npx lighthouse https://ваш-сайт.ru --chrome-flags="--headless" --output=json - run: | result=$(npx lighthouse https://ваш-сайт.ru --chrome-flags="--headless" --output=json) performanceScore=$(echo $result | jq '.categories.performance.score') if [ $(echo "$performanceScore < 90" | bc) -eq 1 ]; then echo "Performance score is too low: $performanceScore" exit 1 fiНастройте бюджет веса Используйте Webpack Bundle Analyzer:
npm install --save-dev webpack-bundle-analyzer// webpack.config.js const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; module.exports = { plugins: [ new BundleAnalyzerPlugin({ analyzerMode: 'static', reportFilename: './dist/bundle-report.html', }), ], };Цель: Держать общий вес пакета < 500 КБ (для мобильных).
Объедините фронтенд и бэкенд-метрики
- Отслеживайте
TTFB(Time to First Byte) с серверной стороны (например, через New Relic). - Сравнивайте
FCP(фронтенд) иTTFB(бэкенд) — еслиTTFB> 300мс, проблема на сервере.
- Отслеживайте
Обучение команды
- Добавьте проверку производительности в PR-ревью (например, "Добавил ли ты
loading="lazy"к изображениям?"). - Организуйте внутренний "Performance Hackathon" с наградами за лучшие оптимизации.
- Добавьте проверку производительности в PR-ревью (например, "Добавил ли ты
Итог: три ключевых правила
- Измеряй всё. Без метрик оптимизация — это гадание.
- Начинай с сетевых запросов. 80% проблем — в том, как и что загружается.
- Не жертвуй UX ради краткосрочных оптимизаций. Если пользователь видит "скелетон-скрин" 2 секунды — это нормально, если после этого всё грузится быстро.
Практическое задание:
- Запустите Lighthouse для своего приложения.
- Найдите одну метрику, которая хуже средней (например,
CLS> 0.1). - Исправьте её за один день (например, добавьте
loading="lazy"к изображениям или оптимизируйте шрифты). - Повторите тест — результат должен улучшиться на 30%+.
Производительность — это не фича, а основа. Если вы её игнорируете, пользователи уйдут, а конкуренты обгонят вас на старте.