01 / 03 cover-1-audit.png
Экран 1 из 3 Экран 2 из 3 Экран 3 из 3

AI-SEO аудит и подготовка перехода на PHP 8.3 для сайта на MODX Evolution

Аудит и техническая ревизия сайта петербургской мастерской по изготовлению памятников — каталог на несколько сотен позиций, MODX Evolution, классический серверный рендеринг без JS-фреймворков. Формальная задача называлась «AI-SEO аудит»: разобраться, насколько сайт готов к тому, что его читают не только люди и Googlebot, но и краулеры GPTBot, ClaudeBot, PerplexityBot и AI-поиск вроде Perplexity и ChatGPT Search. По факту работа разделилась на два трека — SEO/технический аудит с правками в файлах и снипетах, и отдельным заходом — разбор пути обновления самой CMS, которая работала на PHP 7.2 без патчей безопасности шестой год.

Задача

Нужно было пройти чек-лист, который обычно называют «AI-SEO»: доступность контента без выполнения JavaScript, явные правила для AI-ботов в robots.txt, файл llms.txt, структурированные данные Schema.org, корректные canonical и sitemap. Клиент предоставил файловую копию сайта и дамп базы — рабочего сетевого доступа к продакшену не было, поэтому весь аудит контента и вся правка кода велись офлайн, а для полевой проверки уже на живом сайте (HTTP-заголовки, реальные статус-коды, доступность для ботов) был написан отдельный CLI-скрипт, который клиент мог запустить сам одной командой с сервера или любой машины с интернетом.

Что поправили сразу

  • robots.txt. Старый файл закрывал только служебные каталоги и не содержал явных правил ни для одного AI-бота — GPTBot, ClaudeBot, PerplexityBot, Google-Extended и другие технически не были заблокированы, но и не были явно разрешены, а значит поведение было непредсказуемым при любой будущей правке. Файл переписан с явными группами под каждого бота, добавлен запрет на индексацию админки CMS (она не была закрыта — упущение, которое к AI не имеет отношения, но обычно первым идёт в списке проблем), закрыты служебные каталоги на диске и параметры вида utm_, которые иначе плодят дубли страниц в индексе.
  • llms.txt. Файла не существовало (проверено — 404 на живом сайте). Создан по актуальной спецификации: краткое описание компании, разделы каталога, несколько статей — все ссылки собраны из дампа базы и указывают на реально существующие канонические адреса, без выдуманных или служебных URL.
  • JSON-LD. На сайте была только частичная микроразметка (itemscope/itemtype без JSON-LD). Написан снипет MODX, который генерирует Organization, WebSite и BreadcrumbList на каждой странице и Product/Offer — на карточке товара, из тех же данных, что уже выводятся видимым текстом, то есть без «невидимого» контента, которого нет на странице. FAQPage собирается условно: если поле с вопросами-ответами в CMS не заполнено, блок вообще не выводится, а не публикуется с шаблонным или выдуманным содержанием. Код обёрнут в try/catch — при любой ошибке снипет возвращает пустую строку и не роняет страницу.
  • Сжатие ответа. В конфигурации сервера не было ни mod_deflate, ни PHP-сжатия — HTML отдавался несжатым. Добавлен блок Gzip/Deflate для HTML, CSS, JS, SVG и шрифтов.

Ревизия перед сдачей

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

  • Двойной слэш в canonical. В закэшированной копии head-чанка формула сборки canonical содержала лишний слэш перед идентификатором документа. MODX сам добавляет слэш при генерации ссылки, так что на внутренних страницах canonical реально указывал на адрес с двойным слэшем — а такой адрес на сайте открывается с кодом 200, то есть canonical вёл с нормального URL на его собственный технический дубль. Источник правки — не сам кэш (его редактировать бессмысленно, он перезаписывается), а исходный чанк head в CMS.
  • Задвоенный URL в JSON-LD. В новом снипете разметки строки, собирающие ссылки для BreadcrumbList, Product.url и Offer.url, склеивали базовый адрес сайта с результатом функции MODX, которая уже возвращает полный URL сама. На выходе получались ссылки вида «домен/домен/страница». Поправлено в трёх местах снипета.
  • Логическая ошибка в robots.txt. Из первой версии файла следовало, что отдельные группы для Googlebot, YandexBot и AI-ботов «наследуют» ограничения общей группы User-agent: *. Это не так: по спецификации robots.txt специфичная группа для конкретного бота полностью заменяет общую, а не дополняет её. На практике это значило, что перечисленные боты видели бы только разрешение и не учитывали бы запрет на админку, служебные каталоги и кэш. Решение — не плодить отдельные группы под каждого бота, а оставить одну корректную группу с одним набором правил, которую по спецификации применяют все боты, для которых нет отдельной именной группы.

Заодно повторной проверкой sitemap.xml обнаружены дубли: из 394 записей — 381 уникальный адрес, шесть URL повторялись от двух до пяти раз. Похоже на несколько документов CMS с одинаковым alias. Простого array_unique в генераторе недостаточно — сначала нужно найти и развести конфликтующие документы в самой CMS, поэтому это отмечено отдельным пунктом для клиента, а не исправлено вслепую поверх симптома.

Что осознанно не тронуто

На странице контактов телефон и e-mail в видимом тексте не совпадают с адресами в ссылках tel: и mailto: — причём почтовый домен в ссылке вообще не принадлежит сайту, похоже на многолетнюю опечатку. Это ровно тот случай, когда молча «исправить на то, что выглядит правильным» — риск закрепить в Schema.org и на сайте неверный номер или адрес. Правка подготовлена отдельным закомментированным блоком в SQL-миграции и не применяется, пока владелец не подтвердит, какие контакты актуальны. По той же причине не тронут блок FAQ: выдуманные вопросы-ответы от имени компании исказили бы контент. Вместо этого разметка FAQPage уже готова и заработает сама, как только в CMS появятся реальные вопросы клиентов.

Второй трек: путь к PHP 8.3

Сайт работал на Evolution 1.4.17 (релиз — март 2022) и PHP 7.2.34, которая без патчей безопасности с ноября 2020 года. По факту, а не по памяти, проверены версии CMS: ветка 1.4.x жива, но с 1.4.17 вышел всего один патч — 1.4.18, совместимый с PHP от 7.0. Актуальная линия — 3.5.7, но она требует PHP не ниже 8.3, то есть это не «обновить CMS», а отдельный проект с переходом на мажорную версию PHP и мажорную версию CMS одновременно.

Прежде чем что-то мигрировать, весь код сайта — 947 файлов — прогнан через синтаксическую проверку PHP в контейнере с версией 8.3. Синтаксис ломается у 21 файла, и они не разбросаны случайно: 18 — в модуле импорта из Excel, построенном на библиотеке PHPExcel, заброшенной с 2017 года; ещё два — в старом самописном превьюдере изображений внутри плагина эпохи MODX 1.x; последний — в ядровом файле, который заменится самим обновлением. Важная оговорка: проверка синтаксиса ловит только синтаксис, снятые по умолчанию в PHP 8.x поведения она не увидит, реальный объём правок будет больше — но масштаб уже понятен: это не «переписать сайт», а разобраться с одним заброшенным аддоном.

Два стенда рядом

Для проверки миграции подняты два параллельных контура: копия текущего сайта на Evolution 1.4.17 / PHP 7.2.34 и вторая копия — на Evolution 3.5.7 / PHP 8.3, с отдельными портами, базами и томами, без пересечений. Штатный установщик CMS рассчитан на обновление уже существующей версии 3.x, а не на переход с 1.4, поэтому часть шагов пришлось доделывать вручную: собрать файл подключения к базе по образцу старого конфига, перенести настройки, проставить историю миграций (из 60 файлов, воспроизводящих схему с 2018 года, 36 совпадали с уже существующей структурой и были помечены выполненными без повторного прогона, остальные 26 — реальные изменения поздних версий — отработали штатно) и пересобрать служебную таблицу иерархии документов.

Результат: все проверенные типы страниц отдают 200, ни одной фатальной ошибки или предупреждения PHP 8.3 в выводе, все правки, сделанные в рамках этого же аудита — JSON-LD, FAQ-блок, ленивая загрузка изображений — пережили переезд без дополнительных правок.

Один открытый вопрос

После переноса нашлась одна регрессия: генерация внутренних ссылок отдаёт числовой id документа вместо человекочитаемого пути, при этом входящая маршрутизация исправна — прямые адреса открываются штатно, проблема только в сборке исходящих ссылок. Настройки friendly URL, алиасы документов и таблица иерархии проверены и в порядке — причина в том, что в собранном кэше нет карты «alias → id», хотя остальные настройки в кэш попадают. Это зафиксировано как открытый вопрос конкретно для сценария «обновление с 1.4 на 3.x», а не как что-то, что можно тихо обойти: следующий шаг — пересборка через административную панель самой CMS, а если не поможет — обращение в трекер разработчиков Evolution.

Результат

Клиент получил не общее «сайт стал лучше», а конкретный список: что изменено и проверено — robots.txt, llms.txt, JSON-LD, sitemap, canonical, сжатие; что осознанно не тронуто и почему — контакты, FAQ; и что требует его собственного решения или доступа, которого нет у подрядчика — настройка WAF, регистрация в Bing Webmaster Tools, применение SQL-миграции к боевой базе. Отдельно — рабочий стенд с полным прогоном на PHP 8.3 и посчитанный объём правок для перехода на актуальную версию CMS вместо гадания «наверное, заведётся».