Содержание

8 страниц из 185: разбор индексации Astro-блога

Разбор по данным Search Console: какие ошибки нашлись в Astro-блоге и почему статус «просканирована, но пока не проиндексирована» не объясняет причину исключения.

В отчёте Search Console на 19 сентября 2026 у artka.dev было 8 проиндексированных страниц и 177 непроиндексированных. Из них 112 в состоянии «просканирована, но пока не проиндексирована». Карты сайта были обработаны успешно, все 70 адресов из них отдавали 200, у каждой проверенной страницы сканирование было разрешено и канонический адрес совпадал с выбранным Google. Я разобрал сайт по частям и нашёл три настоящие поломки, одна из которых жила в плагине обработки ссылок. Эти находки не установили точную причину исключения 112 страниц из индекса.

Обновление 11 октября 2026: курс Claude Code намеренно удалён 10 октября. Его страницы, Markdown-двойники и старые адреса уроков теперь завершаются ответом 410 Gone; восстановление уроков и результаты 301 → 200 ниже относятся к предыдущей выкладке. Сохранившиеся Markdown-двойники статей открыты для обхода и отдают X-Robots-Tag: noindex вместе с Link: <адрес HTML-статьи>; rel="canonical". Примеры адресов и цифры сентябрьского отчёта оставлены как история проверки; новых результатов Search Console здесь нет.


1. Отчёт: 112 страниц «просканирована, но пока не проиндексирована»

Цифры на 19 сентября 2026, ресурс sc-domain:artka.dev.

СостояниеСтраниц
Проиндексировано8
Просканирована, но пока не проиндексирована112
Не найдено (404)30
Вариант страницы с тегом canonical13
Страница с переадресацией9
Обнаружена, не проиндексирована9
Заблокировано в robots.txt2
Запрещено тегом noindex1

За 90 дней: 8 кликов, 35 показов, средняя позиция 9,9, два запроса в отчёте, оба по названию бренда.

Сто двенадцать страниц в состоянии «просканирована, но пока не проиндексирована» означают, что Google просканировал эти адреса, но на дату отчёта не включил их в индекс. Справка Google по отчёту индексирования не раскрывает точную причину: страницы могут быть проиндексированы позже или остаться вне индекса. Проверка URL по каждой из них давала одинаковую картину: сканирование разрешено, получение успешно, индексирование разрешено, канонический адрес пользователя совпадает с выбранным Google. По этим проверкам препятствий доступу не нашлось; причины исключения они не установили.

Одна деталь оказалась важнее всех остальных. Страница /blog/, список статей, последний раз сканировалась 25 мая. Разбор я делал 19 сентября: почти четыре месяца робот на неё не заходил.

Это и задало порядок работы. Сначала исключить доступ, потом искать, что робот видит вместо материала.


2. Доступ: три задачи, которые часто путают

Первое, что проверяют при жалобе на индексацию, это robots.txt. И первое, что там обычно находят, это смешение трёх разных задач в одном файле.

ЗадачаЧто проверять
Появление в Google SearchGooglebot, HTTP-ответ, noindex, canonical и полезность страницы
Использование для обучения моделейПравила соответствующего обучающего бота
Получение страницы по запросу пользователяПоведение конкретного сервиса и его бота-загрузчика
Одну публичную страницу запрашивают три группы роботов. Поисковые: Googlebot, OAI-SearchBot, Claude-SearchBot. Обучающие модели: GPTBot, ClaudeBot, Google-Extended. Приходящие по запросу пользователя: ChatGPT-User, Claude-User.Одну публичную страницу запрашивают три группы роботов. Поисковые: Googlebot, OAI-SearchBot, Claude-SearchBot. Обучающие модели: GPTBot, ClaudeBot, Google-Extended. Приходящие по запросу пользователя: ChatGPT-User, Claude-User.
Схема 1. Три группы роботов для одной страницы

У OpenAI GPTBot относится к обучению, OAI-SearchBot к поиску, а ChatGPT-User получает страницы по действию пользователя, и для таких запросов robots.txt может не применяться. В документации Anthropic перечислены ClaudeBot, Claude-SearchBot и Claude-User. Google-Extended переключателем индексации в Google Search не работает: назначение токена нужно смотреть в списке роботов Google.

Разрешение одного бота ничего не говорит о настройке остальных. И ни одно из этих разрешений не отвечает на вопрос, почему Google выбрал или не выбрал страницу для индекса.

Отдельная ловушка: директиву noindex нельзя прятать за Disallow. Чтобы прочитать её в HTML, робот должен получить страницу. Если обход запрещён, директива остаётся непрочитанной, и Google объясняет это ограничение прямо. Поэтому внутренний поиск на сайте остаётся открытым для обхода и закрывается через noindex.

Проверять ответ сервера нужно обычным GET, не HEAD:

curl -sS -D /tmp/headers.txt https://artka.dev/blog/ -o /tmp/page.html

В заголовках смотрим статус и отсутствие нежелательного X-Robots-Tag: noindex, в HTML собственный canonical и отсутствие нежелательного meta robots. Если есть переадресация, проверяем и конечный адрес: первый 301 умеет вести на 404. Подмена User-Agent через curl показывает ответ сервера на выбранную строку, но не доказывает доступ настоящего Googlebot, потому что обратный прокси может отдельно проверять происхождение запроса.

Про llms.txt короткий ответ: Google уточнил, что для Google Search он не требуется и на видимость не влияет. На artka.dev этот файл живёт как каталог материалов для клиентов, которые умеют его читать, и всё. Действующая политика доступа лежит в robots.txt: именованные группы в ней повторяют ограничения закрытых маршрутов, потому что ограничения из группы для всех более конкретную группу не дополняют.

В моём случае у проверенных страниц блокировок доступа не нашлось. Статус «просканирована» подтверждал, что обход состоялся; текущий доступ проверялся отдельно.


3. Где ломалось: ссылки, которых робот не видел

Три находки, по возрастанию неприятности.

3.1. Лента статей отдавала роботу две трети архива

Список /blog/ рендерил на сервере четыре карточки, остальное подтягивалось скриптом из кусков /blog/partials/<номер>/. Куски отдавались с заголовком X-Robots-Tag: noindex, а обычной ссылки на вторую страницу в разметке не было вообще, как и блока noscript.

Для робота, который не исполняет скрипты, архив заканчивался на четвёртой статье. При шести опубликованных это стоило двух статей. Доля скрытого растёт линейно:

доля скрытых статей=1−размер страницыN\text{доля скрытых статей} = 1 - \frac{\text{размер страницы}}{N}

При двадцати статьях и размере страницы четыре без ссылок остаются шестнадцать из двадцати. Спасали архивы тегов и блоки «похожее» внутри статей, то есть случайность, а не замысел.

3.2. Сорок адресов с тем же текстом

На момент разбора у каждой статьи и каждого урока был markdown-двойник по тому же адресу с суффиксом .md, и ссылка на него стояла в шапке страницы. Двойники отдавались с кодом 200, типом text/markdown, без X-Robots-Tag и без указания канонического адреса. Комментарий в коде маршрута утверждал, что заголовок ответа несёт канонический адрес; функция, которая этот ответ собирает, ставила только тип содержимого.

Около сорока адресов с полной копией текста. Поскольку файлы раздавались как статика, заголовки к ним нельзя было прикрепить, и правило в robots.txt оказалось удобнее: у каждого ИИ-агента в файле была своя группа, поэтому запрет в группе для всех закрывал двойники для Google и Bing и не трогал остальных.

3.3. Плагин канонизации ссылок пропускал голую относительную форму

Это самая интересная находка. В отчёте Search Console висели тридцать адресов вида:

/courses/claude-code-guide/11-models-and-pricing/12-travel-agent-blueprint
/courses/claude-code-guide/08-tool-calls-and-loop/09-subagents
/en/courses/claude-code-guide/14-claims-verification/02-context-and-cache

Урок внутри урока. Источник нашёлся в плагине, который приводит внутренние ссылки к каноническому виду. Проверка на входе выглядела так:

if (!href.startsWith("/") && !href.startsWith("./") && !href.startsWith("../")) return href;

Ссылка ./09-subagents обрабатывалась и превращалась в ../09-subagents/. Голая 09-subagents проваливалась мимо всех проверок и уходила в разметку как есть. При настройке trailingSlash: "always" адрес урока заканчивается слешем и ведёт себя как каталог, поэтому браузер и робот приклеивали цель к нему.

Плагин канонизации не распознаёт ссылку без ведущего слеша, и она уходит как есть. Адрес урока ведёт себя как каталог, получается вложенный несуществующий путь, и в Search Console появляется 404.Плагин канонизации не распознаёт ссылку без ведущего слеша, и она уходит как есть. Адрес урока ведёт себя как каталог, получается вложенный несуществующий путь, и в Search Console появляется 404.
Схема 2. Как ссылка без ведущего слеша превращается в 404

Разница между двумя написаниями одного и того же была случайной. Починка в одну строку: уравнять голую форму с ./ и проверять на собственную схему (javascript:, data:, mailto:), а не на три конкретных префикса.

Тридцать адресов при этом уже разошлись по чужим ссылкам и по памяти робота, поэтому к правке плагина добавилось правило восстановления: если два последних отрезка адреса оба являются известными слагами уроков, приклеенный хвост и есть цель. Одно правило вместо перечисления всех пар, которых при четырнадцати уроках и пяти формах приставки набирается 980.

Результат той выкладки на живом сайте: 27 из 30 адресов доходили до настоящей страницы. Оставшиеся три отдавали 404 намеренно, включая /terms, которого никогда не было. Переадресация несуществующей страницы на похожую по смыслу это мягкий 404, а не исправление. После удаления курса 10 октября адреса его уроков завершаются 410 Gone.

3.4. Что изменилось в цифрах

Исторические замеры сняты запросами к рабочему сайту, до описанной выкладки и после неё, до удаления курса 10 октября. Текущая политика Markdown-двойников описана в обновлении в начале статьи.

ПроверкаДоПосле
Ссылок на статьи в HTML /blog/46
Ссылок на статьи в HTML главной46
Ответ /blog/partials/2/200 с noindex404
Склеенный адрес урока404301, конечная страница 200
Markdown-двойники для поисковых роботовоткрытызакрыты правилом в robots

Одна оговорка про переадресацию. Search Console хранит адреса в форме без завершающего слеша, а на такой форме получается два перехода: сначала сервер добавляет слеш, потом срабатывает правило восстановления. Роботы проходят оба без потерь, но «одного 301» для этой формы нет, и проверка со слешем этого не покажет. Проверять нужно ровно ту форму, которая лежит в отчёте.


4. Разметка: граф, который не сходился

У статьи есть автор, сайт и собственный адрес. Когда эти сведения собираются в разных компонентах, легко получить двух авторов с разными адресами или дату обновления, не совпадающую с видимой страницей. Единый граф JSON-LD удобен тем, что такие расхождения заметны.

Сам по себе @graph не гарантирует ни индексацию, ни расширенный сниппет, ни цитирование. Несколько отдельных корректных блоков тоже допустимы. Выбор одного графа это решение о сопровождении кода.

Сокращённый пример связей:

{
  "@context": "https://schema.org",
  "@graph": [
    { "@type": "Person", "@id": "https://artka.dev/#person", "name": "Artyom Kashuta" },
    {
      "@type": "WebSite",
      "@id": "https://artka.dev/#website",
      "url": "https://artka.dev/",
      "name": "artka.dev"
    },
    {
      "@type": "WebPage",
      "@id": "https://artka.dev/blog/example/#webpage",
      "url": "https://artka.dev/blog/example/",
      "isPartOf": { "@id": "https://artka.dev/#website" }
    },
    {
      "@type": "BlogPosting",
      "@id": "https://artka.dev/blog/example/#article",
      "author": { "@id": "https://artka.dev/#person" },
      "mainEntityOfPage": { "@id": "https://artka.dev/blog/example/#webpage" }
    }
  ]
}

Инвариант: адрес страницы в графе, в canonical, в карте сайта и во внутренних ссылках обозначает одну и ту же форму URL. На этом сайте с завершающим слешем. У английской версии собственный адрес под /en/, языки связываются через hreflang, а не подменой canonical на русский оригинал.

Для вставки JSON внутрь HTML нужен сериализатор, который не даёт строке </script> закрыть элемент раньше времени. Склейка строк из frontmatter здесь не подходит.

Теперь про найденную поломку. На каждой странице статьи узел BlogPosting ссылался через isPartOf на узел блога с идентификатором https://artka.dev/#blog-ru. Сам этот узел выводился только на /blog/. То есть единственная связь статьи с изданием не разрешалась ни на одной статье обоих языков: ссылка висела в пустоте ровно там, ради чего граф и городился.

Похожая история на страницах проектов: у коллекции не было поля breadcrumb, хотя крошки на странице выводились, а у страницы проекта не было mainEntity, поэтому описание работы висело само по себе.

Вывод для проверок сборки: валидатор разметки такие вещи не ловит, потому что каждый отдельный узел корректен. Ловит обход графа с поиском ссылок @id, для которых в этом же документе нет узла. Такая проверка пишется за полчаса и поймала бы обе поломки сразу. Оговорка: ссылки на узел другого языка по замыслу указывают на другой документ и должны быть в списке разрешённых исключений, иначе проверка будет шуметь.

И честная граница. Google описывает структурированные данные как способ объяснить содержимое и получить право на поддерживаемые формы показа; показ не гарантируется. Расширенный результат по FAQPage из галереи убран, и раздел вопросов остаётся полезным читателю и языковым моделям, но поискового бонуса за него обещать нельзя.


5. Что отдаётся роботу готовым

Робот, который не исполняет скрипты, должен получать содержание из разметки. Диаграммы на техническом сайте тут показательны: если готовить их при сборке, читателю не нужно грузить рендерер и превращать исходник в изображение, а роботу не нужно ничего исполнять.

На artka.dev это rehype-mermaid в пайплайне Astro 7:

import { defineConfig } from "astro/config";
import { unified } from "@astrojs/markdown-remark";
import mdx from "@astrojs/mdx";
import rehypeMermaid from "rehype-mermaid";

export default defineConfig({
  integrations: [mdx()],
  markdown: {
    processor: unified({
      rehypePlugins: [[rehypeMermaid, { strategy: "img-svg", dark: true }]],
    }),
  },
});

Astro 7 сменил Markdown-процессор по умолчанию, поэтому для remark и rehype-плагинов пайплайн unified сохраняется явно; это описано в руководстве миграции. Фрагмент сокращённый: рабочая конфигурация содержит ещё обработку внутренних ссылок, заголовков, изображений и формул.

После сборки диаграмму видно в HTML в dist/client/, и опубликованная страница с отключённым JavaScript её сохраняет. Цена схемы: рендерер работает в окружении сборки, значит браузер и его системные зависимости ставятся в CI до сборки, а версия браузера согласуется с версией Playwright из lockfile.

Если хочется измерить, сколько это стоит, измеряйте честно. Зафиксируйте коммит, версии Node и зависимостей, машину и число блоков. Сравните один и тот же набор страниц в двух копиях: с рендерингом и с заранее подготовленными SVG. Разделите установку зависимостей, первый запуск в новом окружении, повторную сборку и полное время CI. Один холодный и один тёплый запуск не позволяют приписать разницу диаграммам: меняются и другие кеши.

Альтернатива для явных файлов-артефактов, mermaid-cli, умеет собирать Mermaid-блоки Markdown в SVG и ссылки на них:

mmdc -i readme.template.md -o readme.md

Выбор между ним и rehype зависит от организации контента: диаграмма рядом с абзацем удобнее в пайплайне, отдельный набор схем удобнее файлами. Полный состав пайплайна этого сайта описан на странице проекта.


6. Чего всё это не лечит

Найденные ошибки и причина исключения из индекса — разные вопросы. Правки выше исправили ссылки, выдачу архива и разметку. Их влияние на индексацию 112 страниц этим разбором не установлено.

На 19 сентября 2026 из 35 адресов русской карты сайта ровно один содержал текст длиннее тысячи слов. Остальные — статьи короче пятисот слов и уроки курса по Claude Code по 250–400 слов, занимавшие 30 адресов из 35. Это описание объёма материалов, а не доказательство причины исключения. Google прямо пишет, что предпочитаемого числа слов нет. Короткий урок может полностью отвечать на вопрос; длинная статья может оставлять его без ответа.

Практическая ценность материалов — гипотеза для проверки, а не установленный диагноз этого сайта. Проверять стоит конкретный пробел: хватает ли читателю объяснения, примера и способа проверить результат. Добавлять слова ради порога в тысячу слов из этих данных не следует.

Для статуса «просканирована, но пока не проиндексирована» Google также указывает, что повторно отправлять URL на сканирование не требуется. Запрос повторного обхода не объясняет причину исключения и не гарантирует включение в индекс.

Рабочая последовательность остаётся такой:

  1. Проверить текущий доступ, дату последнего обхода и canonical. Статус описывает состоявшийся обход, а настройки сайта могли измениться после него.
  2. Убедиться, что внутренние ссылки ведут на страницы, а робот видит их без скриптов.
  3. Проверить копии текста по другим адресам и их канонические URL.
  4. Починить разметку так, чтобы она описывала то, что на странице видно.
  5. Проверить полноту ответа на каждой странице. Если найден конкретный пробел, добавить нужное объяснение, пример или проверку результата.

Технические проверки дают воспроизводимый результат: ссылка ведёт на нужный URL, сервер отдаёт страницу, разметка соответствует содержимому. Это подтверждает исправление конкретной ошибки. Оно не доказывает, что именно эта ошибка мешала индексации, и не обещает изменения статуса.


Итог

Из 185 известных Google адресов в индексе было восемь. На дату разбора проверенные страницы были доступны, адреса из карты сайта отдавали 200, карты сайта были обработаны, канонические адреса совпадали. Три настоящие поломки нашлись в коде: список статей скрывал часть архива от робота без исполнения скриптов, около сорока адресов дублировали текст в Markdown, а плагин канонизации ссылок пропускал одну из двух эквивалентных форм относительной ссылки и порождал тридцать несуществующих адресов.

Все три исправлены. Это результат технического разбора, а не доказанная причина исключения 112 страниц. Точная причина их исключения остаётся неизвестной. Число слов её не устанавливает, и увеличение объёма не гарантирует индексацию. Следующий шаг — проверять конкретные страницы и дополнять материал там, где читателю не хватает ответа или рабочего примера.


Источники:

FAQ

Что значит «Страница просканирована, но пока не проиндексирована»?

Google просканировал страницу, но пока не включил её в индекс. Она может попасть туда позже или остаться вне индекса. Сам статус не раскрывает точную причину: из него нельзя вывести, что виноваты длина текста, качество материала или разметка. Доступ и canonical проверяют отдельно, с учётом даты последнего обхода.

Может ли robots.txt быть причиной, если страница просканирована?

Статус «просканирована» подтверждает состоявшийся обход, но не текущие настройки robots.txt. Их нужно проверить отдельно. У блокировки в robots.txt есть свой статус в отчёте. Правила для GPTBot, ClaudeBot и других ИИ-агентов не управляют доступом Googlebot: это разные группы правил.

Помогает ли запрос индексирования через проверку URL?

Можно запросить повторный обход, но это не гарантирует индексацию. Для статуса «просканирована, но пока не проиндексирована» Google пишет, что повторно отправлять URL на сканирование не требуется. Сам запрос не устраняет найденные ошибки и не объясняет причину исключения.

Нужен ли llms.txt для индексации в Google?

Нет. Google уточнил, что этот файл не требуется для Google Search и не влияет на видимость или позиции. Как дополнительный каталог материалов для клиентов, которые умеют его читать, он полезен, но он не заменяет HTML, карту сайта и обычные ссылки.

Почему относительная ссылка в Markdown может давать 404?

При настройке trailingSlash always адрес страницы заканчивается слешем, то есть ведёт себя как каталог. Ссылка вида 09-subagents без ведущего слеша приклеивается к текущему адресу и даёт несуществующий вложенный путь. Написание ./09-subagents обрабатывается плагинами канонизации ссылок, а голая форма может проскочить мимо проверок.