Четыре недели Google не индексировал ни одной новой страницы на сайте клиента. Ни ошибок в Search Console. Ни проблем с краулингом. Сайтмап показывал статус «успешно». Я не заметил, пока не поискал блог-пост по точному названию и не получил ноль результатов.
Корневая причина — одна строка в vercel.json.
Симптом
Я опубликовал 3 новых блог-поста за месяц. Ни один не появился в Google. Старые посты продолжали ранжироваться нормально. Сайт был здоров — ни 500 ошибок, ни проблем с деплоем, быстрая загрузка.
Я проверил Google Search Console:
- Покрытие: нет ошибок
- Сайтмап: «Отправлен и обработан» ✓
- Последний краулинг: 4 недели назад
Последняя строка была подсказкой. Google закраулил сайтмап месяц назад и не вернулся. Но статус сайтмапа говорил «успешно». Почему Google перестал бы рекраулить сайтмап, который он успешно обработал?
Расследование
Шаг 1: Я открыл URL сайтмапа в браузере.
https://client-site.com/sitemap.xml
Отрендерилась главная страница. Не XML — настоящая HTML-главная.
Шаг 2: Проверил curl-ом, чтобы исключить браузерные редиректы.
curl -v https://client-site.com/sitemap.xml
< HTTP/2 200
< content-type: text/html; charset=utf-8
URL сайтмапа вернул 200 с text/html. Сайтмап отдавался как HTML-страница. Google «успешно» его забрал, увидел HTML и молча перестал пытаться парсить как сайтмап.
Шаг 3: Я проверил vercel.json.
Блок rewrites исключал sitemap из перезаписи URL. Но блок redirects — нет. Редиректы выполняются до реврайтов. /sitemap.xml перехватывался редиректом и перенаправлялся на /en/sitemap.xml.
/en/sitemap.xml не существовал. Next.js рендерил catch-all 404 (который возвращал 200 из-за отдельной проблемы — неправильно настроенной not-found страницы). Google видел 200 HTML-ответ и отмечал сайтмап как «обработан».
Фикс
Два изменения:
- Добавил
sitemapиrobotsв regex-исключение редиректов (не только реврайтов) - Исправил not-found страницу, чтобы реально возвращала 404-статус
После деплоя:
curl -I https://client-site.com/sitemap.xml
HTTP/2 200
content-type: application/xml
Корректно. XML-сайтмап отдаётся напрямую из Next.js app/sitemap.ts.
Почему не поймал раньше
-
Vercel деплоит молча. Нет валидации
vercel.jsonпротив твоих реальных роутов. Правило редиректа было технически валидным — просто матчило больше URL, чем планировалось. -
Google Search Console сказал «успешно». 200-ответ с HTML — не ошибка для процессора сайтмапов Google. Он просто молча игнорирует не-XML контент. Ни предупреждения, ни алерта.
-
Старые страницы продолжали ранжироваться. Google уже проиндексировал старый контент. Невидимым был только новый — а это замечаешь через недели.
-
Локальная разработка не использует vercel.json.
next devигнорирует Vercel-специфичные правила редиректов. Сайтмап работал идеально локально.
Чеклист предотвращения
После этого инцидента я добавил в деплой-пайплайн:
# Smoke-тест после деплоя
SITEMAP_TYPE=$(curl -s -o /dev/null -w "%{content_type}" https://yoursite.com/sitemap.xml)
if [[ "$SITEMAP_TYPE" != *"xml"* ]]; then
echo "ALERT: Sitemap is not returning XML"
exit 1
fi
ROBOTS_TYPE=$(curl -s -o /dev/null -w "%{content_type}" https://yoursite.com/robots.txt)
if [[ "$ROBOTS_TYPE" != *"text/plain"* ]]; then
echo "ALERT: robots.txt is not returning text/plain"
exit 1
fi
Две проверки curl. 10 секунд на выполнение. Поймали бы это на первом деплое, а не через 4 недели.
Мета-урок
Редиректы и реврайты vercel.json обрабатываются в порядке: headers → redirects → rewrites → файловая система. Если редирект матчится до исключения реврайта — исключение никогда не запускается. А если твой catch-all роут возвращает 200 вместо 404, каждый сломанный редирект становится невидимым — выглядит рабочим, потому что код ответа говорит OK.
Тестируй инфраструктурные роуты (/sitemap.xml, /robots.txt, /api/*) после каждого изменения vercel.json. Не только роуты приложения.
Беспокоишься о тихих SEO-проблемах на своём Next.js-сайте? Напиши — я запускаю пост-деплой проверки на каждом клиентском проекте именно для того, чтобы ловить этот класс невидимых багов.