Миграция сайта с WordPress на Headless CMS
Миграция с WordPress на headless CMS — не просто смена платформы. Это пересмотр архитектуры: отделение хранилища контента от фронтенда, изменение рабочего процесса редакторов, другой деплой. Требует планирования, поэтапного выполнения и периода параллельной работы.
Оценка до начала
Перед выбором целевой CMS нужно ответить:
- Сколько типов контента (Custom Post Types) и полей (ACF)?
- Используются ли плагины с кастомной логикой (WooCommerce, Events Calendar)?
- Какой уровень технической компетенции у контент-команды?
- Каков бюджет на лицензии (Contentful, Sanity) vs. self-hosted?
- Нужна ли поддержка черновиков и preview в новой CMS?
Сравнение целевых платформ
| Критерий | Contentful | Sanity | Strapi | KeystoneJS |
|---|---|---|---|---|
| Хостинг | SaaS | SaaS/Self | Self | Self |
| Лицензия | От $300/мес | От $15/мес | Open Source | Open Source |
| Редактор | Хороший | Отличный | Базовый | Базовый |
| API | REST + GraphQL | GROQ + GraphQL | REST + GraphQL | GraphQL |
| Медиа | CDN включён | CDN включён | Своё хранилище | Своё |
Этап 1: Аудит и маппинг контента (1–2 недели)
Инвентаризация существующего контента:
# Экспорт из WordPress через WP-CLI
wp export --post_type=post,page,product --status=publish --path=/var/www/html
# Анализ ACF-полей
wp acf field-group export --group_id=all --output=json > acf-fields.json
# Статистика по типам
wp post list --post_type=post --format=count
wp post list --post_type=page --format=count
Маппинг WordPress → целевая CMS:
# mapping.yaml
wordpress_types:
post:
target: blogPost
fields:
post_title: title
post_content: body (RichText)
post_excerpt: excerpt
post_date: publishedAt
_thumbnail_id: featuredImage (Asset)
categories: categories (Reference[])
tags: tags (Reference[])
acf.seo_title: seoTitle
acf.seo_description: seoDescription
product:
target: product
fields:
post_title: name
_regular_price: price (Number)
_stock_qty: stock (Number)
product_cat: categories
acf.gallery: gallery (Asset[])
Этап 2: Настройка новой CMS (1 неделя)
Создание Content Types в целевой CMS точно по маппингу. Настройка валидаций, локализации, ролей.
Этап 3: Скрипт миграции (1–2 недели)
// scripts/migrate-from-wp.ts
import axios from 'axios';
import * as contentful from 'contentful-management';
import TurndownService from 'turndown';
const turndown = new TurndownService({ headingStyle: 'atx' });
const cmaClient = contentful.createClient({ accessToken: process.env.CMA_TOKEN! });
async function migratePosts() {
const space = await cmaClient.getSpace(process.env.SPACE_ID!);
const env = await space.getEnvironment('master');
// Получаем посты из WP REST API
let page = 1;
while (true) {
const { data: posts } = await axios.get(
`${WP_URL}/wp-json/wp/v2/posts?per_page=100&page=${page}&_embed`
);
if (!posts.length) break;
for (const wpPost of posts) {
await migratePost(env, wpPost);
await delay(200); // Rate limiting
}
page++;
}
}
async function migratePost(env: any, wpPost: any) {
const featuredImageUrl = wpPost._embedded?.['wp:featuredmedia']?.[0]?.source_url;
let imageAsset;
if (featuredImageUrl) {
imageAsset = await uploadAsset(env, featuredImageUrl, wpPost.title.rendered);
}
const entry = await env.createEntry('blogPost', {
fields: {
title: { 'en-US': wpPost.title.rendered },
slug: { 'en-US': wpPost.slug },
body: { 'en-US': turndown.turndown(wpPost.content.rendered) },
excerpt: { 'en-US': wpPost.excerpt.rendered.replace(/<[^>]*>/g, '') },
publishedAt: { 'en-US': wpPost.date },
...(imageAsset && {
featuredImage: { 'en-US': { sys: { type: 'Link', linkType: 'Asset', id: imageAsset.sys.id } } },
}),
},
});
await entry.publish();
console.log(`Migrated: ${wpPost.title.rendered}`);
}
Этап 4: Миграция медиафайлов
// Скачиваем и загружаем все медиафайлы WP
async function migrateMedia() {
const { data: media } = await axios.get(`${WP_URL}/wp-json/wp/v2/media?per_page=100`);
for (const item of media) {
const asset = await env.createAsset({
fields: {
title: { 'en-US': item.title.rendered },
description: { 'en-US': item.alt_text },
file: { 'en-US': {
contentType: item.mime_type,
fileName: path.basename(item.source_url),
upload: item.source_url,
}},
},
});
await asset.processForAllLocales();
await asset.publish();
mediaIdMap[item.id] = asset.sys.id; // для связей
}
}
Этап 5: Параллельный запуск и переключение
- Запустить новый фронтенд на staging с реальными данными
- Провести редизайн-ревью с контент-командой
- Настроить автоматическую синхронизацию WordPress → новая CMS на период перехода
- DNS-переключение в low-traffic период
- Отключить WordPress через 2–4 недели после стабилизации
Сроки типовой миграции
| Этап | Небольшой сайт (<500 записей) | Средний (500–5000) | Крупный (5000+) |
|---|---|---|---|
| Аудит и маппинг | 1 неделя | 1–2 недели | 2–4 недели |
| Настройка CMS | 3–5 дней | 1 неделя | 1–2 недели |
| Скрипт миграции | 1 неделя | 1–2 недели | 2–4 недели |
| Тестирование | 3–5 дней | 1 неделя | 2 недели |
| Запуск | 1 день | 1–2 дня | 1 неделя |
| Итого | 4–6 недель | 6–10 недель | 3–5 месяцев |







