Разработка · 7 мин чтения

Как получить Lighthouse 100 на лендинге Next.js: чек-лист оптимизации

Lighthouse 100 на лендинге Next.js - не разовая удача с перезагрузкой теста, а результат десятка конкретных решений: от формата картинок до того, как подключён виджет чата. За пару лет я принял и сам собрал не один десяток лендингов на Next.js, и там, где разработчик поставил next/image и на этом остановился, Performance стабильно держался на 60-75 баллах. Ниже - чек-лист, по которому я проверяю проект перед сдачей, с порогами метрик и ошибками, которые режут баллы даже на простом одностраничнике.

С чего начинается аудит: метрики Core Web Vitals и их пороги

Перед тем как что-то чинить, я всегда смотрю на конкретные цифры, а не на итоговый балл - 87 баллов ничего не говорит о том, что чинить в первую очередь. Открываю DevTools, ставлю throttling Slow 4G + 4x CPU slowdown (так Lighthouse эмулирует мобильный телефон) и смотрю на четыре метрики.

Метрика Порог для 100 баллов Типичное значение «из коробки»
LCP (Largest Contentful Paint) до 2,5 с 3,5-5 с
CLS (Cumulative Layout Shift) до 0,1 0,15-0,3
TBT (Total Blocking Time) до 200 мс 400-900 мс
INP (Interaction to Next Paint) до 200 мс 250-500 мс

Важный нюанс: локальный Lighthouse и PageSpeed Insights показывают lab-данные, полученные в конкретном прогоне, а вкладка Field Data в PSI берёт реальные замеры пользователей из CrUX за последние 28 дней. Балл 100 в лабораторных условиях не гарантирует такой же результат в полевых данных - если на сайт заходят с медленного мобильного интернета в регионах, разрыв будет заметным. Я всегда прогоняю тест 3-5 раз подряд и беру медиану, потому что один прогон может дать разброс в 5-8 баллов из-за случайной нагрузки на машину.

Изображения и шрифты - 30-40 баллов, которые теряют почти все

Самая частая причина просевшего LCP на лендинге - герой-картинка. Стандартный <img> без размеров и без приоритета грузится поздно и сдвигает макет. С next/image это решается, но с оговорками: картинка, которая попадает в LCP (обычно это первый экран), обязана получить priority, иначе Next.js поставит её в ленивую очередь и потеряет секунду-полторы.

import Image from 'next/image'
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin', 'cyrillic'], display: 'swap' })

export default function Hero() {
  return (
    <Image
      src="/hero.jpg"
      alt="Лендинг компании"
      width={1200}
      height={630}
      priority
      sizes="100vw"
    />
  )
}

По шрифтам: next/font с самохостингом снимает лишний DNS-резолв к Google Fonts и убирает мигание текста, но только если задан display: 'swap' и подключены реально нужные subset’ы - кириллица весит на 30-40% больше латиницы, и если тянуть оба набора без надобности, шрифтовой файл растёт до 150-200 КБ. Отдельно проверяю CLS от шрифтов: если у запасного шрифта другая ширина символов, текст «прыгает» при подгрузке - лечится через font-display: swap в паре с похожим по метрикам fallback-шрифтом (Arial вместо Inter даёт почти нулевой сдвиг).

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

JavaScript и бандл: откуда берётся TBT и лишняя гидрация

TBT почти всегда упирается в объём JS, который браузер должен распарсить и выполнить до First Input. На лендинге в 5-7 экранов клиентский JS редко должен весить больше 100-150 КБ в gzip. Смотрю на это через next build - вывод показывает вес каждой страницы и shared chunks, а для деталей подключаю @next/bundle-analyzer.

Что обычно режет TBT на 200-400 мс:

  • Секции ниже первого экрана (отзывы, FAQ, карта) - через next/dynamic с ssr: false, если это не критично для SEO-текста внутри.
  • Полновесные UI-киты вместо точечных импортов - иконки из lucide-react по одной, а не весь пакет.
  • Сторонние виджеты: кнопка оплаты эквайринга, калькулятор доставки СДЭК, онлайн-чат. Это то, что почти всегда вешают в <head> синхронным тегом, хотя это внешние блокирующие скрипты, которые не нужны до взаимодействия пользователя.
import Script from 'next/script'

export default function ChatWidget() {
  return (
    <Script
      src="https://widget.example.com/chat.js"
      strategy="lazyOnload"
    />
  )
}

Server Components снимают часть проблемы сами

Если лендинг собран на App Router и большая часть блоков - статический контент без интерактивности (текст, карточки услуг, отзывы), они по умолчанию рендерятся на сервере и не попадают в клиентский бандл вообще. Интерактивным оставляю только форму заявки и, может быть, слайдер - это уже само по себе срезает TBT в разы по сравнению с Pages Router, где всё гидрируется скопом.

Accessibility и Best Practices - баллы, которые сливают по невнимательности

Эти две категории обычно берутся без архитектурных изменений, но именно на них теряют 5-15 баллов из-за мелочей. Проверяю по списку: alt на всех изображениях (даже декоративных - тогда пустой alt=""), контраст текста не ниже 4.5:1 для обычного шрифта, aria-label на кнопках-иконках без текста (гамбургер-меню, стрелка слайдера), размер тач-таргетов от 48x48px на мобильной версте.

Best Practices чаще всего проседает из-за console-ошибок (обычно от стороннего скрипта аналитики или пиксельного кода) и устаревших API вроде document.write, который иногда тянут старые версии виджетов чатов. Ещё одна частая причина - картинки без явного aspect-ratio, из-за которых Lighthouse ругается на layout shift даже при формально верном width/height, если CSS их переопределяет.

SEO-чеклист для сотки в категории SEO

Здесь баллы почти всегда про метаданные и техническую доступность для краулера: заполненный title и meta description через Next.js Metadata API, canonical на каждой странице, валидный viewport, читаемый текст без микрошрифтов ниже 12px.

Отдельно - robots.txt и sitemap.xml. Для статических лендингов проще всего сгенерировать их автоматически при сборке, а не поддерживать руками: готовый конфиг для next-sitemap, который я использую на своих проектах, лежит в библиотеке готовых скриптов - подключается за пять минут и не требует правок при каждом новом URL.

Хостинг и деплой: как инфраструктура влияет на цифры

Даже идеально оптимизированный код упирается в то, где и как отдаётся HTML. Для лендинга, у которого контент меняется редко, я почти всегда выбираю статическую генерацию (SSG) или ISR с длинным ревалидейтом вместо SSR на каждый запрос - рендерить страницу заново при каждом визите ради лендинга без персонализации смысла нет.

Вариант хостинга TTFB на практике Что учитывать
Vercel (Edge) 30-80 мс CDN и сжатие из коробки, но free-тариф режет функции по времени
VPS + Docker + Nginx 80-200 мс нужно вручную настроить brotli, HTTP/2 и кэш-заголовки
Shared-хостинг с Node 200-400 мс частая причина «плавающего» TTFB под нагрузкой

На VPS я обычно донастраиваю Nginx на brotli вместо gzip (экономит ещё 15-20% трафика на JS/CSS) и выставляю Cache-Control с длинным max-age на статику из _next/static - эти файлы хешируются по содержимому и безопасно кэшируются на год.

Если собираете лендинг с нуля и хотите сразу закладывать эти пороги в архитектуру, а не чинить постфактум, у меня можно заказать лендинг на Next.js с оптимизацией под Core Web Vitals на этапе вёрстки - переделывать готовый сайт под сотку почти всегда дороже и дольше, чем учесть это сразу.

Быстрый SEO-лендинг под продукт

Лендинг на Next.js

от 80 000 ₽

Подробнее →

Частые вопросы

Сколько времени занимает довести готовый лендинг на Next.js до 100 баллов Lighthouse?

Если сайт уже собран на Next.js и проблема в деталях - картинки, шрифты, лишние скрипты - обычно хватает 1-2 дней точечной работы. Если упор в архитектуру (SSR там, где нужен SSG, тяжёлый UI-кит, синхронные сторонние виджеты в head), доработка растягивается на неделю и больше, потому что меняется не разметка, а логика загрузки данных.

Реально ли получить 100/100 именно на мобильном тесте, а не только на десктопе?

Да, но мобильный тест жёстче за счёт throttling - те же 400 КБ JS, которые незаметны на десктопе, на эмуляции медленного 4G и слабого CPU превращаются в 300-400 мс TBT. Я всегда ориентируюсь на мобильный результат как на основной, десктопный почти всегда получается сам по себе легче.

Почему Lighthouse показывает разные цифры при каждом запуске?

Это нормально - тест лабораторный, и на результат влияет загрузка машины, сеть, фоновые процессы браузера. Разброс в 3-7 баллов между прогонами - обычное дело. Для честной оценки прогоняю тест несколько раз и беру медиану, а для боевого сайта смотрю ещё и полевые данные CrUX в PageSpeed Insights - они точнее отражают, что видят реальные посетители.

Стоит ли переезжать с Tilda или WordPress на Next.js только ради Lighthouse 100?

Если лендинг на Tilda напичкан сторонними блоками и Tilda-скриптами, потолок по Performance обычно в районе 65-85 баллов - платформа сама подгружает свой рантайм, который не убрать. На WordPress ситуация похожая из-за плагинов. Для 100 баллов во всех категориях нужен полный контроль над бандлом, а его даёт только кастомная сборка вроде Next.js. Но если сотка не критична для бизнеса и устраивает 85-90 баллов, переезд ради самих цифр обычно не окупается - дешевле точечно доработать текущий сайт.

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Самозанятый Калинкин Н. А. · работаю с физлицами и юрлицами

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно