IT
🚀

Улучшение Core Web Vitals на практике — Как сократить LCP с 3 секунд до 1.2 секунды

USD/JPY分散は、為替急変局面で一方通貨の過大シェアを防ぎ、月次の再バランスと上限規則で感情的な一括投資を抑える実践設計です。

Улучшение Core Web Vitals на практике — Как сократить LCP с 3 секунд до 1.2 секунды

Краткое содержание

  • LCP (Largest Contentful Paint) — это ключевой показатель, который напрямую влияет на рейтинг в поиске Google.
  • Мы последовательно применили три метода: оптимизацию изображений, стратегию загрузки шрифтов и код-сплиттинг, чтобы сократить LCP с 3.0 секунд до 1.2 секунд.
  • CLS был снижен с 0.28 до 0.04 за счет явного указания размеров для изображений и рекламных областей, а также устранения изменений в макете.
  • Это основано на реальном проекте Next.js, и те же принципы можно применить к проектам на React и Vue.

Введение — Почему Core Web Vitals важны?

monitor screengrab seo analytics seo analytics

В 2021 году Google выпустил обновление Page Experience, и Core Web Vitals стали не просто показателями UX, а элементами ранжирования SEO. С тех пор Google постоянно увеличивает вес этих сигналов, и к 2024 году INP (Interaction to Next Paint) полностью заменит FID, что также изменит критерии оценки.

Мой проект электронной коммерции еще полгода назад имел ужасный мобильный рейтинг в 38 баллов по PageSpeed Insights. LCP составлял 3.0 секунды, CLS — 0.28, INP — 320 мс — все три показателя находились в зоне "требуется улучшение". В этой статье я расскажу, как я изменил эти показатели и зафиксирую весь процесс.


Как быстро понять три показателя Core Web Vitals?

computer screen bunch data on it

LCP — скорость "первого впечатления" страницы

LCP — это время, необходимое для рендеринга самого большого элемента контента в области просмотра (обычно это изображение героя или текст H1). По стандартам Google хорошим считается значение ниже 2.5 секунд, а плохим — выше 4.0 секунд.

На сайтах электронной коммерции чаще всего изображение баннера героя является объектом LCP. Если это изображение не загружается вовремя, все остальные оптимизации становятся бессмысленными.

CLS — не "прыгает" ли макет?

CLS (Cumulative Layout Shift) измеряет степень неожиданного перемещения элементов во время загрузки страницы. Если рекламный баннер вставляется слишком поздно или изображение загружается без указания размеров, показатель увеличивается. Хорошим считается значение ниже 0.1.

INP — насколько быстро реагирует на пользовательский ввод?

INP измеряет задержку ответа на все взаимодействия, такие как клики, нажатия и ввод с клавиатуры. В марте 2024 года он заменил FID, и хорошим считается значение ниже 200 мс. Чем тяжелее JavaScript-бандлы, тем больше блокируется основной поток, что ухудшает INP.


Диагностика проблемы — Первоначальные измерения

close up computer screen blurry background

Точность записи показателей до оптимизации — это отправная точка. Я использовал следующие инструменты по порядку:

  1. 1PageSpeed Insights — одновременно предоставляет полевые данные (данные реальных пользователей) и лабораторные данные (Lighthouse)
  2. 2Chrome DevTools > Вкладка Performance — проверка временной шкалы рендеринга и кандидатов на LCP
  3. 3WebPageTest — реальное измерение на основе CDN-узлов в стране, визуальная проверка с помощью фильмстрима
  4. 4Vercel Analytics / Sentry — сбор Core Web Vitals на основе реальных пользовательских сессий

По результатам диагностики узкие места были определены в трех местах.

  • Изображение героя — 4.2MB JPEG, вставлено напрямую с помощью тега без оптимизации
  • Google Fonts — синхронная загрузка 3-х семейств шрифтов с помощью @import
  • Размер бандла — основной чанк 1.8MB, включает множество библиотек без применения tree-shaking

Как сократить LCP с 3.0 секунд до 1.2 секунд?

Метод 1 — Оптимизация изображений и preload

Это дало наибольший эффект. Я полностью изменил способ обработки изображения героя.

До

html
<img src="/banner.jpg" alt="Главный баннер" />

После — компонент Image Next.js + конвертация в WebP

jsx
import Image from 'next/image';

<Image
  src="/banner.webp"
  alt="Главный баннер"
  width={1920}
  height={800}
  priority          // Изображение, являющееся объектом LCP, должно быть с приоритетом
  quality={80}
  sizes="(max-width: 768px) 100vw, 1920px"
/>

Добавление только свойства priority позволяет Next.js автоматически вставить тег для этого изображения. После конвертации в WebP размер файла уменьшился с 4.2MB до 340KB, что составляет примерно 92% уменьшения.

Если у вас чистый проект HTML/React, вы можете добавить тег preload напрямую в .

html
<link
  rel="preload"
  as="image"
  href="/banner.webp"
  type="image/webp"
/>

Эта работа позволила снизить LCP с 3.0 секунд до 1.8 секунд.

Метод 2 — Изменение стратегии загрузки шрифтов

Если загружать Google Fonts с помощью @import, браузер останавливается на парсинге CSS и отправляет запрос на шрифты. Это время ожидания значительно увеличивает LCP.

До

html
<style>
  @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap');
</style>

После — + display=swap + собственный хостинг

html
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
  rel="stylesheet"
  href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap"
/>

Более того, используя next/font (Next.js 13+), можно обрабатывать шрифты на этапе сборки, что полностью устраняет внешние запросы.

js
// app/layout.js
import { Noto_Sans_KR } from 'next/font/google';

const notoSansKR = Noto_Sans_KR({
  weight: ['400', '700'],
  subsets: ['latin'],
  display: 'swap',
});

После улучшения шрифтов LCP снизился с 1.8 секунд до 1.5 секунд.

Метод 3 — Код-сплиттинг и оптимизация бандла

Если основной JS-бандл слишком велик, основной поток браузера блокируется на парсинге и выполнении. В это время элемент LCP не отображается на экране.

Динамический импорт для разделения начального бандла

jsx
import dynamic from 'next/dynamic';

// Тяжелый компонент графика, который находится ниже по прокрутке
const SalesChart = dynamic(() => import('@/components/SalesChart'), {
  ssr: false,
  loading: () => <div className="chart-skeleton" />, 
});

Анализ бандла для удаления ненужных библиотек

bash
npx @next/bundle-analyzer

В результате анализа было обнаружено, что включены moment.js (66KB gzip) и вся библиотека lodash (70KB gzip). Я заменил moment на необходимые функции из date-fns, а lodash переключил на импорт с tree-shaking из lodash-es.

js
// До
import _ from 'lodash';
const result = _.groupBy(data, 'category');

// После
import { groupBy } from 'lodash-es';
const result = groupBy(data, 'category');

Основной бандл уменьшился с 1.8MB до 420KB, а LCP снизился с 1.5 секунд до 1.2 секунд.


Снижение CLS с 0.28 до 0.04

Основными виновниками перемещения макета были два момента.

1. Изображения без указанных размеров

Я указал width и height для всех тегов , или зафиксировал aspect-ratio с помощью CSS.

css
.product-image-wrapper {
  aspect-ratio: 4 / 3;
  overflow: hidden;
}

2. Поздно вставляемые рекламные баннеры

Я заранее зафиксировал плейсхолдер одинаковой высоты до загрузки рекламного слота.

jsx
<div style={{ minHeight: '90px' }}>
  <AdBanner slot="header" />
</div>

Эти два изменения позволили снизить CLS с 0.28 до 0.04.


Улучшение INP — снижение нагрузки на основной поток

INP естественным образом улучшился после уменьшения бандла с 320 мс до 140 мс. Дополнительно я отложил тяжелые вычисления внутри обработчика событий клика с помощью requestIdleCallback.

js
button.addEventListener('click', () => {
  // Немедленное обновление UI
  setLoading(true);

  // Тяжелая обработка откладывается на время простоя
  requestIdleCallback(() => {
    processHeavyData(payload);
  });
});

Сравнение до и после оптимизации

ПоказательДо оптимизацииПосле оптимизации

🔧 Related Free Tools

Похожее