SEO для сайтов на React и Next.js: что важно
«React плох для SEO» — один из самых живучих мифов веб-разработки. Доля правды в нём есть, но относится она к одному конкретному способу использовать React, а не к самому React.
Если у вас сайт на React или Next.js или вы только выбираете технологию, по сути вопросов два. Видит ли поисковик содержимое страницы, не запуская JavaScript? И в порядке ли сайт технически, как любой другой хорошо сделанный сайт? Первое — вопрос архитектуры, второе — вопрос чек-листа.
Откуда взялся миф
Классическое React-приложение (SPA, single-page application) отдаёт с сервера почти пустой HTML: один <div> и ссылку на файл скрипта. Всё содержимое — заголовки, тексты, ссылки — появляется только после того, как браузер скачал и выполнил JavaScript.
Человеку всё равно: он видит готовую страницу. Поисковому роботу — нет.
Google умеет рендерить JavaScript, но на отдельном этапе. Сначала он читает HTML, а рендеринг происходит позже, когда до него доходят ресурсы. Обычно это срабатывает. Но если скрипт падает с ошибкой, запрос данных не укладывается во время или какой-то файл закрыт в robots.txt, Google проиндексирует то, что было в HTML, — то есть ничего.
Многие другие не рендерят вовсе. Большинство других краулеров, роботы ИИ-сервисов и превью ссылок (Facebook, LinkedIn, Slack, WhatsApp) читают только исходный HTML. Для них страница, отрисованная на клиенте, пуста: у отправленной ссылки нет заголовка и картинки, а ИИ-поиску нечего цитировать.
Значит, проблема не в React, а в том, где собирается HTML.
Решение: HTML собирается на сервере или при сборке
Два способа, и оба сразу отдают роботу готовое содержимое:
- Серверный рендеринг (SSR) — сервер собирает HTML на каждый запрос.
- Статическая генерация (SSG) — HTML собирается заранее, во время сборки (build), а сервер просто отдаёт готовый файл.
Next.js делает это по умолчанию. В App Router компоненты по умолчанию серверные, а страница генерируется статически, если ей не нужны данные конкретного запроса. Каждый URL возвращает настоящий HTML с содержимым; JavaScript лишь добавляет поверх интерактивность (это называется гидратацией).
Быстрая проверка: есть ли текст в HTML?
Откройте страницу, нажмите Ctrl+U (на Mac Cmd+Option+U) и найдите на ней какую-нибудь фразу. Не через инструменты разработчика — они показывают страницу уже после выполнения JavaScript. То же из командной строки:
curl -s https://vash-sayt.ee/uslugi/ | grep -c "фраза, которая видна на странице"
Если результат 0, содержимое появляется только в браузере. Инструмент проверки URL в Search Console дополнительно показывает, какой HTML получил Google после рендеринга.
Чек-лист для сайта на React и Next.js
Серверный HTML — это условие, а не вся работа. Вот что должно быть на месте на каждой странице.
Свой title и meta description на каждой странице. Самая частая ошибка SPA — один и тот же заголовок на всех URL. В Next.js его задаёт экспорт metadata, а на динамических страницах — generateMetadata, и он попадает в <head> уже на сервере, а не позже.
Canonical. Каждая страница сообщает, какой URL — её официальный адрес. Иначе варианты с параметрами запроса или со слешем в конце могут конкурировать как дубли одной страницы.
hreflang на многоязычном сайте. Каждая языковая версия ссылается на все остальные и на x-default, и ссылки должны быть взаимными. На нашем собственном сайте пять языков с переведёнными URL, и каждая страница получает canonical и hreflang из одного места — вручную это не удержать в порядке на пяти языках.
Генерируемые sitemap.xml и robots.txt. В Next.js это файлы app/sitemap.ts и app/robots.ts. Карта сайта строится из тех же данных, что и страницы, поэтому новую страницу в ней невозможно забыть. Наш сайт генерирует sitemap во время сборки.
Структурированные данные (JSON-LD). Компания, услуги, статьи, FAQ и хлебные крошки в блоке <script type="application/ld+json">. Он должен быть в исходном HTML, а не добавляться позже на клиенте.
Настоящие ссылки. Google переходит по ссылкам <a href>, по кнопкам он не кликает. Компонент Link в Next.js отрисовывает нормальный элемент <a>. Навигация, которая работает только через onClick и router.push(), для робота невидима.
Настоящий 404. Отсутствующая страница должна отдавать HTTP-код 404, а не 200 с текстом «страница не найдена» — второе Google называет мягкой ошибкой 404 (soft 404). В Next.js за это отвечает notFound(). При статическом экспорте хостинг должен отдавать страницу 404 с правильным кодом.
Одно правило для слеша в конце. /uslugi и /uslugi/ — два разных URL. Выберите один вариант (trailingSlash в Next.js), второй перенаправьте 301-м редиректом и следите, чтобы внутренние ссылки использовали ту же форму. Если новый сайт заменяет старый, посмотрите также, как сделать редизайн сайта без потери SEO.
Изображения с размерами и alt-текстом. width и height заранее резервируют место, и содержимое не прыгает (CLS). next/image всё равно их требует. При статическом экспорте автоматическая оптимизация изображений в Next.js отключена — картинки нужно заранее сжать до разумного размера.
Клиентские компоненты — только там, где есть взаимодействие. 'use client' отправляет компонент в браузер вместе со всем, что он импортирует. Меню, форме и фильтру это нужно; текстовому блоку и подвалу — нет.
Скорость: ловушки, характерные для React
Общая картина Core Web Vitals разобрана в статье скорость сайта: как её измерить и улучшить. У сайта на React сверх этого есть три собственные ловушки.
Объём JavaScript и гидратация
Даже страница, отрисованная на сервере, скачивает JavaScript и гидратируется. Пока это не закончилось, кнопки не реагируют — это бьёт по INP, особенно на недорогих телефонах. Каждый клиентский компонент и каждая библиотека (карусель, библиотека анимаций, библиотека для работы с датами) увеличивают бандл. Тяжёлый виджет внизу страницы стоит загружать только тогда, когда он нужен (next/dynamic).
Не прячьте заголовок за JavaScript
Распространённый приём: содержимое плавно «проявляется», а для этого изначально стоит opacity: 0, пока скрипт не разрешит его показать. Если так поступить с главным заголовком страницы, LCP — главный показатель скорости загрузки у Google — будет ждать, пока скачается JavaScript и пройдёт гидратация. А если скрипт не загрузится, заголовок так и останется невидимым.
Мы усвоили это на собственном сайте. Заголовок главной страницы — LCP-элемент, и теперь у него чистая CSS-анимация, которая стартует с первой отрисовки, а не появление по команде JavaScript. Эффекты при прокрутке ниже по странице — отдельная история, заголовок от них не зависит.
Не отправляйте в браузер все языки
На многоязычном сайте легко случайно импортировать в клиентский компонент весь словарь переводов. Тогда каждый посетитель скачивает тексты на всех языках. Правильнее так: серверный компонент читает текст и передаёт клиентскому через props только нужные ему строки.
Статический экспорт: каждая страница — готовый файл
Next.js умеет экспортировать весь сайт в чистый HTML (output: 'export'). На выходе — папка файлов, которую отдаст любой веб-сервер или CDN.
- Быстро — в момент посещения сервер ничего не вычисляет.
- Безопасно — нет базы данных, админки и серверного кода, которые можно атаковать.
- Дёшево в размещении — хватает обычного статического хостинга.
Наш собственный сайт — полностью статический экспорт: Next.js (App Router), React и TypeScript, пять языков.
Плата за это: каждое изменение содержимого означает новую сборку и загрузку. Для сайта компании и блога, которые меняются раз в неделю, это отлично подходит. Если содержимое меняется каждую минуту, у пользователей есть аккаунты или нужен поиск по базе данных, понадобится серверный рендеринг или отдельный сервис. Контактной форме тоже нужен небольшой серверный обработчик.
Если вы сейчас на WordPress и думаете о переезде, прочитайте переход с WordPress без потери SEO и более общее сравнение WordPress или сайт на коде.
Когда звать на помощь
Если у вас уже есть сайт на React, сделайте три проверки: посмотрите исходный код трёх страниц, сравните число проиндексированных страниц в Search Console с реальным числом страниц и проверьте, отличаются ли заголовки от страницы к странице. Если текста нет в HTML, правка метатегов не поможет — нужно перестраивать рендеринг, часто с переходом с SPA на Next.js.
Мы делаем сайты на Next.js так, чтобы всё перечисленное было заложено с самого начала. Если сайт уже есть, но в Google не двигается, начните с SEO-продвижения.
