Міграція на Shopify без втрати позицій: 3000 товарів з OpenCart
Як каталог на 3000 товарів переїхав зі старого OpenCart на headless Shopify + Next.js без жодної втраченої позиції — 1:1 URL, редиректи, hreflang і структуровані дані.
Зміна платформи — момент, коли гине SEO більшості магазинів. Не тому, що новий стек гірший (зазвичай він значно кращий), а тому, що міграція одночасно й непомітно змінює кожен URL, кожен title, кожне внутрішнє посилання і кожен блок структурованих даних. Google бачить сайт, який не впізнає, і позиції, зароблені за п'ять років, скидаються за два тижні.
MioMio продає понад 170 модних домів — Furla, Casadei, Premiata, Bikkembergs. Каталог жив на старому OpenCart, який ніхто не хотів підтримувати: жодних нормальних точок інтеграції, кожне підключення — хак, жодного шляху до сучасних сервісів. Перебудова — headless Shopify з вітриною на Next.js. Умова: 3000 товарів мали переїхати, не спаливши п'ять років накопиченої пошукової ваги.
Вижили всі. Ось процес, який зробив це нудним, а не щасливим випадком.
Правило, яке визначило решту рішень
Одне правило, ухвалене до першого рядка коду: структура URL лишається ідентичною, 1:1. Не схожою. Не покращеною. Ідентичною.
Це найважливіше рішення будь-якої міграції — і саме ним найчастіше жертвують, зазвичай заради чистішого формату слагів або тому, що нова платформа має власну думку про URL. Кожен такий обмін перетворює безкоштовну міграцію на проєкт з редиректами, а проєкти з редиректами течуть. 301 передає переважну більшість сигналів, але «переважна більшість» на 3000 URL — це вже вимірювана втрата, і будь-який забутий редирект — це жорсткий 404 на сторінці, яка раніше ранжувалась.
Стандартна структура Shopify цьому опирається: вона хоче /products/handle і /collections/handle. Саме headless повертає свободу — маршрутизацією володіє Next.js, а Shopify лишається лише комерційним API позаду. Ідентичність товару живе в Shopify; URL, за яким ця ідентичність віддається, — ваш.
У headless-архітектурі CMS більше не диктує вам URL. Для міграції це не побічний ефект — це і є вся причина йти в headless.
Крок перший: інвентаризувати те, що знає Google
До того як торкатися нового стека, зберіть список URL, які мають значення. Не список URL, які існують — це різні числа, часто на порядок.
Чотири джерела, об'єднані разом:
- →Search Console: кожна сторінка з показами за останні 16 місяців. Це справжня правда про те, що Google проіндексував і на що клікали користувачі.
- →Наявний XML-sitemap — як уявлення самої платформи про те, що має існувати.
- →Повний краул живого сайту: він ловить сторінки, які sitemap забув — старі лендінги кампаній, осиротілі фільтри, пагіновані колекції.
- →Аналітика: будь-який URL з органічними сесіями, зокрема давно викинуті зі sitemap.
Об'єднання цих чотирьох — контракт міграції. Кожен URL із нього в день запуску має віддавати 200 або 301. Жоден не має віддавати 404.
Корисний результат — один файл мапінгу, який лежить у репозиторії поруч із кодом:
// Один рядок на кожен старий URL. Там, де мапінг не механічний — перевіряється руками.
export const REDIRECTS = [
{ from: "/index.php?route=product/product&product_id=482", to: "/uk/furla-metropolis-bag" },
{ from: "/women/bags/furla", to: "/uk/collections/furla" },
// ...
] as constНайчастіше забувають саме старі URL із query-рядком. OpenCart, Magento і старі збірки PrestaShop роками віддавали адреси в стилі ?route= чи ?id_product=, і вони залишались в індексі. У наївному мапінгу «слаг у слаг» їх не буде — а це рівно ті URL, у яких найстаріші й найсильніші зовнішні посилання.
Крок другий: не тягнути двомовність у міграцію
MioMio працює українською та англійською. Багатомовні сайти ламаються під час міграції специфічно: мовні версії трактуються як дублікати, Google довільно обирає одну, і половина каталогу випадає з індексу на іншому ринку.
Ліки неефектні й безкомпромісні — кожна сторінка декларує повний кластер своїх мовних версій, і кожна версія посилається назад:
<link rel="canonical" href="https://example.com/uk/furla-metropolis-bag" />
<link rel="alternate" hreflang="uk" href="https://example.com/uk/furla-metropolis-bag" />
<link rel="alternate" hreflang="en" href="https://example.com/en/furla-metropolis-bag" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/furla-metropolis-bag" />Hreflang працює за трьох умов, і порушення будь-якої тихо вимикає весь кластер:
- 01Взаємність. Якщо сторінка A вказує B як українську версію, B має вказувати A як англійську. Односторонні анотації ігноруються.
- 02Самопосилання. Кожна сторінка включає саму себе у власний список alternate. Саме це правило пропускають, бо воно виглядає надлишковим.
- 03Canonical не суперечить hreflang. Canonical, що вказує на іншу мову, ніж власний запис hreflang сторінки, — це протиріччя, а протиріччя Google вирішує ігноруванням ваших інструкцій.
Генеруйте це однією функцією, яка приймає локаль і шлях. Ніколи не пишіть hreflang руками в шаблонах — рукописний hreflang псується тієї ж миті, коли хтось додає сторінку.
Крок третій: структуровані дані як актив міграції
Структуровані дані зазвичай подають як інструмент rich-результатів — зірочки й ціни у видачі. Під час міграції вони роблять корисніше: дають краулеру недвозначну заяву, що товар за новим URL — це той самий товар, який він уже знав.
Product-схема зі стабільним ідентифікатором — SKU, GTIN, MPN — перенесеним зі старого каталогу, це найсильніший доступний сигнал безперервності. Ідентифікатори не змінюються лише тому, що змінилась платформа.
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Furla Metropolis",
"sku": "FRL-MTP-0412",
"brand": { "@type": "Brand", "name": "Furla" },
"offers": {
"@type": "Offer",
"priceCurrency": "UAH",
"price": "8400",
"availability": "https://schema.org/InStock"
}
}Віддавайте це з сервера. Структуровані дані, вставлені клієнтським JavaScript, читаються непослідовно й додають залежність від рендерингу до сигналу, який найменше хочеться затримувати.
Крок четвертий: послідовність у день запуску
Порядок важливіший за швидкість:
- 01Викотити з мапою редиректів, живою з першого запиту. Не «трохи згодом» — саме перший краул після перемикання DNS формує очікування.
- 02Одразу подати новий sitemap у Search Console, а старий лишити доступним ще на кілька тижнів, щоб Google звів обидва.
- 03Прогнати URL Inspection на десяти-п'ятнадцяти показових сторінках: найтрафіковіший товар, найглибша колекція, обидві мовні головні, пагінований лістинг. Виправити зауваження, не чекаючи агрегованих даних.
- 04Перші 72 години дивитись у логи сервера, а не в аналітику. Логи показують, що саме запитував Googlebot і який статус отримав. Аналітика показує лише людей, які пройшли.
Єдине число, важливе на першому тижні, — частка 404 у логах. Вона має впасти до нуля за кілька днів. Якщо ні — у файлі мапінгу дірки, і кожен день, поки вони відкриті, це день розпаду.
Що міграція дала насправді
Усі 3000 товарів вийшли в продакшн без жодної втраченої позиції. Завантаження сторінки впало з 4,8 с до 1,2 с — учетверо швидше на мобільних, де в модному каталозі і сидить майже весь трафік. Конверсія зросла на 25% у першому кварталі, а SEO-оцінка Google повернулась як 100/100 зі структурованими даними, hreflang і чистими Core Web Vitals.
Варто розділяти причини: позиції збереглись завдяки 1:1 URL і редиректам. Конверсія зросла завдяки швидкості й ребрендингу. Це незалежні перемоги, і їх змішування — те, як команди починають вірити, що редизайн є SEO-стратегією.
Загальний висновок: міграція — не технічна подія, яку треба пережити. Це контракт із краулером, який вам уже довіряє: тримайте адреси стабільними, тримайте ідентичність доказовою — і довіра переїде цілою. Усе інше в перебудові — швидкість, дизайн, редакційний контент — стає чистим приростом поверх уже заробленого.