Чотири тижні 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-сайті? Напиши — я запускаю пост-деплой перевірки на кожному клієнтському проєкті саме для того, щоб ловити цей клас невидимих багів.