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 баллов, переезд ради самих цифр обычно не окупается - дешевле точечно доработать текущий сайт.