Перейти к контенту
Neaptidestudio
блог

Метатеги Next.js в body: проверка девяти условий

Neaptide · 12 октября 2026 г. · 8 мин чтения

Проверили Next.js 15.5.22 в девяти условиях: исходный HTML, DOM браузера и положение canonical. Показываем результаты и границы выводов для SEO.

В этой статье
Сравнение положения метаданных Next.js в head и body: девять условий, восемнадцать HTTP-ответов.

Метаданные после закрывающего тега head могут быть следствием потоковой выдачи Next.js. На нашем локальном стенде с production-сборкой асинхронные метаданные оказались в body для двух профилей запроса и в head для третьего. Значения присутствовали во всех полных ответах, но это ещё не доказывает, что поисковик принимает каждый тег в таком месте.

Сначала определите, какой тег проверяете, с каким профилем запроса и в каком представлении документа. Наш опыт объясняет выдачу метаданных. Для canonical дополнительно учитывайте требование Google к размещению в head: правильного URL где-либо в документе недостаточно, чтобы считать реализацию корректной.

Что мы проверили

12 октября 2026 года мы проверили Next.js 15.5.22 с React и React DOM 19.2.6 и Node v25.2.1. Три режима и три HTTP-профиля User-Agent дали девять условий. Каждое проверили дважды: сохранили 18 полных ответов HTTP 200, заголовки и HTML. В обоих прогонах совпали число тегов, значения и положение.

Сравнивали статический объект metadata, асинхронную generateMetadata со стандартной конфигурацией и ту же функцию с htmlLimitedBots: /.*/. Оба маршрута использовали force-dynamic. Статический здесь — способ задания метаданных, а не предварительная генерация страницы. В асинхронную функцию намеренно добавили задержку 250 мс без внешних запросов. Она позволяет увидеть последовательность выдачи, но не моделирует реальную нагрузку и не измеряет быстродействие.

Ответ сервера зависел от профиля запроса

Матрица девяти условий: статические и блокирующие метаданные находятся в head; стандартные асинхронные — в body для браузерного и Googlebot-профилей и в head для Twitterbot.
Замер 12 октября 2026 года. Каждая ячейка описывает title, description и canonical в двух полных HTTP-ответах. Внутри каждого условия все три тега находились в одной области.
Замер 12 октября 2026 года. Каждая ячейка описывает title, description и canonical в двух полных HTTP-ответах. Внутри каждого условия все три тега находились в одной области.
Режим метаданныхUA браузераUA GooglebotUA Twitterbot
Объект · по умолчаниюheadheadhead
Async · по умолчаниюbodybodyhead
Async · блокирующийheadheadhead

Коллектор передавал строку, похожую на Chrome 134, Googlebot/2.1 или Twitterbot/1.0. Это значения HTTP-заголовка, а не посещения соответствующих сервисов. Все запросы использовали Accept: text/html и Accept-Encoding: identity, без cookies и заголовков клиентской навигации. Точные строки и запросы есть в файле результатов.

Браузер оставил стандартные async-теги в body

Отдельно открыли три режима в подключённом браузере; полученный сервером User-Agent указывал Chrome 154. В DOM теги title, description и canonical находились в head в статическом и блокирующем режимах. В стандартном асинхронном режиме они остались в body: переноса в head мы не наблюдали. Эти три загрузки отделены от 18 HTTP-запросов и не являются проверкой рендеринга Googlebot.

Браузер оставил стандартные async-теги в body
РежимDOM: все три тега
Объект · по умолчаниюhead
Async · по умолчаниюbody
Async · блокирующийhead

Для canonical нужно отдельное решение

Next.js описывает потоковую выдачу метаданных в body и блокирующую выдачу для ботов с ограниченной обработкой HTML. Это объясняет воспроизведённую картину. При этом Google указывает, что HTML-ссылка canonical принимается в head; пример добавления через JavaScript также использует document.head. Общая рекомендация фреймворка не разрешает вопрос о конкретном теге.

В нашем стандартном async-режиме canonical остаётся в body и в ответе сервера, и в DOM. Мы не можем утверждать, что Google принял или отклонил его: стенд работал локально, использовал noindex и не отправлялся на индексацию. Это повод проверить опубликованный маршрут. Если canonical остаётся вне head, выберите реализацию, соответствующую требованиям Google, и повторите проверку. Показанная ниже блокирующая конфигурация дала head на стенде; это проверенный способ размещения, а не доказанный рост позиций.

Как проверить свой маршрут

  1. Сохраните полный ответ production-страницы вместе с заголовками запроса и версией релиза. Проверяйте реальные элементы title, description и canonical: строка внутри React-скрипта не является ещё одним HTML-тегом.
  2. Отдельно проверьте DOM после рендеринга: значения, дубли и принадлежность к head или body. Правильный заголовок вкладки ничего не говорит о canonical.
  3. Сравните профили, важные для сервиса, в котором обнаружена проблема. Разделяйте браузер, подставленную строку робота и реальный запрос внешнего сервиса. Подмена User-Agent проверяет только выбор ответа вашим сервером.
  4. Примените требования к конкретному тегу. Для canonical проверьте опубликованную страницу доступными инструментами Google и сведения о канонизации, обеспечив размещение HTML-ссылки в head. Локальный DOM не показывает, какой canonical выбрал Google.
  5. Измените необходимую часть реализации, пересоберите сайт и повторите те же проверки. Если меняете потоковую выдачу метаданных, оцените загрузку на реальной нагрузке: искусственная задержка нашего стенда не даёт оценки скорости production-сайта.

Конфигурация, которую мы проверили

Для третьего режима пересобрали то же приложение с этой настройкой. Все три тега оказались в head при каждом проверенном профиле. Настройка заменяет стандартное правило сопоставления ботов; /.*/ подходит для любого User-Agent. При внедрении сохраните остальные параметры конфигурации и оцените область изменения.

// next.config.mjs
export default {
  htmlLimitedBots: /.*/,
};

Параметр меняет выдачу метаданных во всём приложении. Мы не проверяли отдельное регулярное выражение только для выбранных ботов, production-кеши или переходы между маршрутами. Этот опыт не обосновывает перевод всех сайтов на блокирующую выдачу.

Как повторить эксперимент

Скачайте стенд с lock-файлом зависимостей. В README приведены команды сборки и запуска обеих конфигураций, затем Python-коллектора. Два сервера запускаются в отдельных терминалах, коллектор — из корня распакованного архива. Задержка, noindex и зарезервированные адреса example.test предназначены только для локального опыта. В архиве доказательств — исходные HTTP-ответы и снимки DOM; в results.json — полные профили и разобранные наблюдения.

Данные для проверки

Проверены одна версия, две локальные сборки и три загрузки в браузере. Мы не тестировали CDN, настоящих удалённых роботов, клиентскую навигацию, ошибки метаданных или Cache Components. Не измеряли индексацию, позиции, цитаты ИИ, трафик, конверсии и Core Web Vitals. Сбор и проверку выполнил один исследователь. Поведение своей версии и окружения нужно перепроверить.

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

Коротко о главном

Почему значения метаданных также есть внутри React-скриптов?

Ответ может содержать сериализованные данные наряду с HTML-элементами. Наш парсер считает элементы title, meta и link, игнорируя похожие строки в скриптах. Проверяйте количество и значения элементов, а не число текстовых совпадений.

Любая async-функция generateMetadata ведёт себя так же?

На стенде явно включён рендеринг при запросе и добавлена задержка. Мы не проверяли предварительно сгенерированные маршруты, кешированные данные и другие версии Next.js. Одного ключевого слова async недостаточно, чтобы воспроизвести все условия опыта.