С WordPress на Payload без потери SEO

Сайт с WordPress на Payload CMS 3 переносится без потери позиций, если выполнить три условия: все посты, страницы, медиа, рубрики и SEO-поля перенести скриптом; на каждый старый адрес, который меняется, поставить 301-редирект; после запуска прогнать сайт по списку старых URL и починить всё, что отдаёт 404. Ниже разбираем, что переносится автоматически, что приходится собирать заново, как выглядит схема в Payload и как проверить, что поисковики ничего не потеряли.

Стоит ли переезжать вообще и чем Payload отличается от WordPress для вашей задачи, мы разобрали отдельно на headless.expert/payload-vs-wordpress. Здесь исходим из того, что решение принято.

Что переносится автоматически, а что руками

У WordPress три способа достать данные: REST API (/wp-json/wp/v2/posts, pages, media, categories, tags), XML-выгрузка из раздела Инструменты, Экспорт и прямой доступ к базе MySQL. Для типового сайта нам хватает REST API: он отдаёт записи постранично по 100 штук, в заголовках X-WP-Total и X-WP-TotalPages видно, сколько всего. К базе идём, когда нужны метаполя плагинов, которые в API не выведены.

ЧтоКак переноситсяКомментарий
Посты и страницыСкриптомЗаголовок, slug, дата публикации, автор, статус, текст
МедиафайлыСкриптомСкачиваем оригиналы, загружаем в коллекцию media с alt и подписью
Рубрики и меткиСкриптомИерархия рубрик сохраняется через поле parent
SEO-поля Yoast и Rank MathСкриптомTitle, description, canonical, noindex, картинка для соцсетей
Поля ACFСкриптом, после разбораКаждое поле получает свой тип в схеме Payload
ШорткодыРуками или по правилам[gallery], [contact-form-7] и самописные превращаются в блоки
Страницы на Elementor, WPBakery, DiviРукамиРазметка конструктора не переносится осмысленно, страницу собираем из блоков заново
ФормыЗановоОфициальный плагин @payloadcms/plugin-form-builder или своя коллекция заявок
WooCommerceОтдельный проектТовары и заказы достаются, а оплата, доставка и касса пишутся заново
ПользователиПо ситуацииДля блога обычно только авторы, им проще задать пароль заново

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

Схема в Payload

Перед скриптом пишется схема: коллекции и поля на TypeScript, которые лежат в репозитории вместе с сайтом.

КоллекцияПоляОткуда в WordPress
poststitle, slug, publishedAt, author, categories, tags, content (richText), heroImage, meta, wpIdwp_posts с типом post
pagestitle, slug, layout (блоки), meta, wpIdwp_posts с типом page
categoriestitle, slug, parent, wpIdwp_terms, таксономия category
tagstitle, slug, wpIdтаксономия post_tag
mediaфайл (upload: true), alt, caption, wpIdвложения, wp-content/uploads
usersemail, имя (auth: true)авторы

Текст статей хранится в поле richText на редакторе Lexical, это редактор по умолчанию в Payload 3. Lexical хранит документ в виде JSON, поэтому HTML из WordPress конвертируется функцией convertHTMLToLexical из пакета @payloadcms/richtext-lexical (ей нужен JSDOM). Картинки из текста конвертер сам не загружает, это прямо написано в документации. Мы сначала переносим все медиа, строим таблицу "старый URL картинки → id в media" и при конвертации подменяем img на блок загрузки. Попутно учитываем, что WordPress вставляет в текст уменьшенные копии вида photo-300x200.jpg, их нужно сводить к оригиналу.

Поле wpId в каждой коллекции хранит старый id из WordPress. По нему скрипт находит и обновляет уже перенесённые записи без дублей, поэтому его можно прогонять сколько угодно раз: на тесте, после правок схемы и финально перед переключением, чтобы забрать посты, опубликованные за время работы. По нему же собираются связи: пост ссылается на рубрику по wpId, скрипт находит новую запись.

Порядок загрузки: медиа, рубрики и метки, авторы, потом посты и страницы. Иначе связям не на что ссылаться.

Для SEO подключаем официальный плагин @payloadcms/plugin-seo: он добавляет к коллекциям группу meta с title, description и картинкой, показывает превью сниппета и счётчик символов. Canonical и noindex добавляем полями через опцию fields того же плагина.

Откуда брать SEO-поля

ПлагинГде лежат данные
Yoast SEOМетаполя _yoast_wpseo_title, _yoast_wpseo_metadesc, _yoast_wpseo_canonical; в REST API готовые значения в поле yoast_head_json
Rank MathМетаполя rank_math_title, rank_math_description, rank_math_canonical_url; эндпоинт /wp-json/rankmath/v1/getHead после включения поддержки headless в настройках

Если в Yoast заголовок задан шаблоном (%%title%% %%sep%% %%sitename%%), в метаполе пусто, а итоговое значение есть только в yoast_head_json. Поэтому для Yoast мы берём данные из API.

Редиректы и таблица адресов

Главный источник потерь при переезде: адреса, про которые никто не вспомнил. Таблицу старых URL собираем из трёх мест: sitemap WordPress (обычно /sitemap_index.xml от Yoast или Rank Math), обход сайта краулером (Screaming Frog или аналог) и выгрузка страниц с показами из Яндекс Вебмастера и Search Console за год. Третий источник находит то, чего нет в sitemap: старые адреса с внешними ссылками.

Что обычно забывают:

Каждому адресу в таблице ставим одно из трёх решений: адрес сохраняется как есть, 301 на новый адрес, 410 для того, что удалено сознательно (пустые метки, архивы авторов). Цепочек не допускаем: старый адрес ведёт сразу на конечный, без промежуточного редиректа.

Хранить редиректы удобно в Payload. Официальный плагин @payloadcms/plugin-redirects добавляет коллекцию redirects с полями from и to, где to может ссылаться на документ, и тогда редирект не сломается, если у поста поменяют slug. Важная деталь из документации: плагин только хранит редиректы, сам редирект выполняет фронт. Мы делаем это в Next.js: для небольших таблиц через redirects в next.config, для сотен и тысяч адресов через middleware или правила в Nginx, сгенерированные из той же коллекции.

Ещё одна деталь Next.js: permanent: true в next.config отдаёт код 308, а не 301. Поисковики обрабатывают их одинаково, но если в ТЗ записано 301, ставим statusCode: 301 явно.

Sitemap и robots.txt

Sitemap генерируется из коллекций Payload файлом app/sitemap.ts в Next.js: адрес, дата последнего изменения из поля updatedAt, записи с noindex исключаются. Один файл вмещает до 50 000 адресов, для больших сайтов Next.js разбивает его через generateSitemaps. Путь sitemap лучше оставить прежним или поставить редирект со старого /sitemap_index.xml, и сразу отправить новый в Яндекс Вебмастер и Search Console.

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

Проверка после запуска

  1. Прогоняем краулером всю таблицу старых адресов: каждый отдаёт 200, 301 с одним переходом или 410, других вариантов нет.
  2. Сверяем title, description и canonical на выборке страниц со старыми значениями из выгрузки.
  3. Проверяем robots.txt, sitemap, микроразметку и Open Graph.
  4. Смотрим картинки: alt перенесён, адреса из wp-content/uploads отвечают редиректом или файлом.
  5. Первые 2-4 недели каждый день смотрим отчёты об ошибках обхода в Вебмастере и Search Console и логи 404 на сервере, новые битые адреса добиваем редиректами.

Скорость после переезда обычно растёт сама: страницы на Next.js отдаются готовыми, без десятка плагинов и их скриптов. Но это бонус, ради которого переезд не планируют. Главное здесь сохранить то, что уже индексировано.

Сроки

Западные студии, которые переносят сайты с WordPress на Payload и публикуют свои плейбуки, называют 2-4 недели разработки для типового бизнес-сайта и 4-8 недель вместе с аудитом, тестом и запуском. Сайты с большим количеством своих типов записей и плагинов у них идут от двух-трёх месяцев. Это чужие цифры, мы приводим их для ориентира. Наш срок зависит от вещей, которые видны только после просмотра админки:

Что нужно от вас

После запуска сайт живёт на одном приложении Next.js с Payload внутри: админка на /admin того же домена, база PostgreSQL, файлы на диске или в S3-совместимом хранилище. Переезды с других систем описаны отдельно: переезд с Firebase и переезд со Strapi. Если на WordPress работает магазин, читайте про магазин на Payload.

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

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

Упадёт ли трафик после переезда с WordPress на Payload?

Если адреса страниц сохранены или на каждый старый адрес стоит 301-редирект, а title, description и canonical перенесены, поисковики переиндексируют сайт без потери позиций. Трафик проседает, когда забывают про рубрики, метки, пагинацию и адреса картинок из wp-content/uploads. Поэтому мы собираем полную таблицу старых адресов до переезда, а не после.

Можно ли сохранить те же адреса страниц?

Да, и это лучший вариант. В Payload у каждой записи есть поле slug, а маршруты во фронте на Next.js задаются как угодно, в том числе /2024/05/slug/ или /category/news/. Редиректы нужны только там, где структуру адресов меняют сознательно.

Переносятся ли SEO-поля из Yoast и Rank Math?

Да. Yoast хранит заголовок и описание в метаполях _yoast_wpseo_title и _yoast_wpseo_metadesc и отдаёт готовые теги в REST API в поле yoast_head_json. Rank Math хранит rank_math_title и rank_math_description. Скрипт переносит их в группу meta, которую добавляет официальный плагин @payloadcms/plugin-seo.

Что делать с WooCommerce при переезде?

Товары, категории и заказы достаются через REST API WooCommerce или CSV-выгрузку, но магазин это отдельный проект: оплата, доставка, касса и склад переписываются заново. Как мы собираем магазины на Next.js и Payload, описано на странице про магазин на Payload.

Сколько длится переезд с WordPress на Payload?

Западные студии, которые этим занимаются, называют 2-4 недели разработки для типового бизнес-сайта и 4-8 недель с учётом аудита и тестов. Срок зависит от числа типов записей, плагинов, страниц на визуальных конструкторах и того, переделывается ли дизайн. Точный срок называем после просмотра админки.

Нужно ли сообщать Яндексу и Google о переезде?

Если домен тот же, инструмент переезда в Яндекс Вебмастере не нужен. Достаточно отправить новый sitemap в Вебмастер и Search Console и следить за отчётами об ошибках обхода первые недели. При смене домена переезд оформляется отдельно в обеих панелях.

Обсудим ваш проект

Нажимая кнопку, вы соглашаетесь на обработку персональных данных для ответа на заявку.