Next.js SSR: как мы подняли Lighthouse с 41 до 97 для высоконагруженного маркетплейса
В начале 2020 года Google объявил: Core Web Vitals становятся сигналом ранжирования. У нас был маркетплейс на Create React App (клиентский рендеринг). Lighthouse на мобильных - 41. Миграция на Next.js с SSR заняла три месяца.
Почему клиентский рендеринг убивает SEO и конверсию
Последовательность загрузки CSR-страницы:
1. Браузер получает пустой HTML: <div id="root"></div>
2. Скачивает React-бандл: 340KB
3. Парсит и выполняет JavaScript
4. Запрашивает данные с API (товары, категории, цены)
5. Рендерит контент
Итого: 4.2 секунды до First Contentful Paint на 4G
Краулер Google видит пустой <div> и должен выполнить JavaScript, чтобы увидеть контент. Google это делает, но с задержкой - иногда неделями до повторного обхода.
Конверсия: каждая дополнительная секунда загрузки снижает конверсию на 7% (исследование Akamai). При 4.2 секундах мы теряли значительную часть выручки.
SSR, ISR или Static: правильный выбор
getServerSideProps - рендер на каждый запрос
// Используйте когда контент меняется при каждом запросе
// (персонализированные цены, остатки в реальном времени)
export async function getServerSideProps({ params }) {
const product = await fetchProduct(params.id);
return { props: { product } };
}
Плюсы: всегда актуальные данные.
Минусы: каждый запрос нагружает сервер, нельзя кэшировать на CDN.
getStaticProps + revalidate (ISR) - лучший выбор для большинства страниц
// Страница товара: обновлять не чаще раза в минуту
export async function getStaticProps({ params }) {
const product = await fetchProduct(params.id);
return {
props: { product },
revalidate: 60, // Разрешить пересборку не чаще раза в 60 секунд
};
}
export async function getStaticPaths() {
// Предсобираем топ-1000 товаров
const topProducts = await fetchTopProducts(1000);
return {
paths: topProducts.map(p => ({ params: { id: p.id.toString() } })),
fallback: 'blocking', // Остальные товары: SSR при первом запросе, потом кэш
};
}
Плюсы: HTML кэшируется на CDN. Первый пользователь после истечения revalidate получает обновлённую страницу в фоне, остальные - из кэша.
Минусы: данные могут устареть на время до revalidate.
ISR - это был наш выбор для страниц товаров. Цены и остатки обновляются максимум раз в минуту - приемлемо для 99% случаев.
12 изменений, которые дали нам 97 Lighthouse
1. SSR вместо пустого div
До: <div id="root"></div>
После: полный HTML с данными товара с первого байта
2. Оптимизация изображений через next/image
import Image from 'next/image';
// До: тег img без оптимизации
<img src={product.image} width="400" height="400" />
// После: автоматический WebP, lazy loading, предотвращение CLS
<Image
src={product.image}
width={400}
height={400}
priority={isAboveFold} // Preload для изображений выше линии сгиба
alt={product.name}
/>
next/image автоматически конвертирует в WebP (на 60% меньше JPEG), добавляет loading="lazy" для изображений ниже, предотвращает сдвиги макета через явные размеры.
3. Динамические импорты для некритичных компонентов
import dynamic from 'next/dynamic';
// Не грузим модальное окно отзывов пока пользователь не нажмёт кнопку
const ReviewModal = dynamic(() => import('../components/ReviewModal'), {
loading: () => null,
ssr: false,
});
4. Устранение Layout Shift (CLS 0.34 → 0.02)
Браузер не знает размер изображения до его загрузки - перестраивает макет. Фикс: всегда задавать явные размеры.
// До: CLS = 0.34, изображение сдвигало текст при загрузке
<img src={banner.url} style={{ width: '100%' }} />
// После: CLS = 0.02, место зарезервировано заранее
<Image src={banner.url} width={1200} height={300} layout="responsive" />
5. CSS Modules вместо глобальных стилей
Мигрировали с глобального CSS (375KB) на CSS Modules. Next.js инлайнит критический CSS в <head>, остальное грузится асинхронно.
Результаты
| Метрика | CRA (CSR) | Next.js ISR |
|---|---|---|
| Lighthouse Mobile | 41 | 97 |
| First Contentful Paint | 4.2s | 0.8s |
| Largest Contentful Paint | 6.1s | 1.4s |
| CLS | 0.34 | 0.02 |
| Total Blocking Time | 890ms | 120ms |
| Органический трафик (через 3 мес.) | базовый | +34% |
| Конверсия | базовая | +18% |
Рост SEO занял 3 месяца - столько Google нужно для повторного обхода. Улучшение конверсии было немедленным: пользователи на медленном интернете (мобильный, 4G) ощутили разницу сразу.
Что ISR изменил в процессах команды
До: обновление товара = коммит → CI → полный ребилд → деплой = 8 минут.
После ISR: цены, остатки и описания обновляются без перезапуска сборки. Контент-менеджер публикует изменения - через 60 секунд они на сайте без участия разработчиков.
В 2024 году Next.js App Router с React Server Components развивает эти принципы дальше. Но ISR в Next.js 9/10 был уже значительным скачком вперёд по сравнению с CRA.