Після переїзду на нову CMS органічний трафік найчастіше падає через адреси, які Google знав роками, а новий сайт віддає на них 404 або перекидає всіх на головну. Сам 301-редирект вагу сторінки не забирає. Нижче покроково описано, як зібрати всі старі URL магазину, скласти з них карту редиректів, прописати її на сервері і перевірити скриптом ще до запуску.
Що насправді відбувається з позиціями під час переїзду
Google переносить сигнали з кожної старої адреси на нову окремо, коли Googlebot пройде обидві. У документації How to move a site Google пише, що для сайту середнього розміру перехід на нові URL у видачі займає кілька тижнів або більше, а для великих сайтів довше. Там само сказано, що 301 та інші постійні редиректи не призводять до втрати PageRank.
Отже, тимчасові коливання позицій нормальні. Тривале падіння має конкретні технічні причини, і майже всі вони видно ще на тестовому сервері:
- частина старих товарних і категорійних URL не потрапила в карту і віддає 404;
- видалені товари масово ведуть на головну, і Google вважає це soft 404;
- редиректи йдуть ланцюжком через проміжні адреси, які лишилися від попередніх переїздів;
- на продакшн разом зі staging переїхали
noindexабоDisallow: /у robots.txt; - замість 301 стоїть 302 або JavaScript-перехід.
Крок 1. Зберіть повний список старих URL
Одного джерела не вистачить. Sitemap показує те, що CMS вважає актуальним, але не містить старих адрес товарів, на які досі ведуть зовнішні посилання. Беріть дані з чотирьох місць і зводьте в одну таблицю без дублів.
- Sitemap.xml старого сайту. Збережіть копію файлу до вимкнення старого магазину.
- Search Console, звіт «Ефективність», вкладка «Сторінки» за 16 місяців. Інтерфейс експортує максимум 1 000 рядків, а Search Analytics API віддає до 50 000 рядків на день для одного сайту (Google Search Central Blog). Для каталогу на кілька тисяч товарів потрібен API або Looker Studio. Окремо вивантажте звіт «Посилання», щоб побачити сторінки з беклінками.
- Логи вебсервера за кілька місяців, з урахуванням сезонних категорій.
- База даних CMS, бо там є всі товари, включно з тими, що зараз не продаються.
Для nginx або Apache зі стандартним форматом логу combined список адрес, які віддавали 200, отримується однією командою:
zcat -f /var/log/nginx/access.log* \
| awk '$9 == 200 {print $7}' \
| sort | uniq -c | sort -rn > old_urls.txt
Не забувайте про зображення і PDF. Google окремо радить переносити їх так само, як сторінки, бо картинки товарів теж отримують трафік з пошуку.
Де OpenCart зберігає адреси товарів і категорій
В OpenCart 3 ЧПУ зберігаються в таблиці oc_seo_url. Поле query містить рядки на кшталт product_id=48 чи category_id=20, а keyword зберігає саму адресу. Запит нижче дає всі товари з їхнім старим шляхом. Товари без ЧПУ отримують адресу у форматі index.php?route=..., бо саме так OpenCart їх показував:
SELECT p.product_id,
pd.name,
COALESCE(CONCAT('/', su.keyword),
CONCAT('/index.php?route=product/product&product_id=', p.product_id)) AS old_path
FROM oc_product p
JOIN oc_product_description pd
ON pd.product_id = p.product_id AND pd.language_id = 1
LEFT JOIN oc_seo_url su
ON su.query = CONCAT('product_id=', p.product_id)
AND su.store_id = 0 AND su.language_id = 1;
Є нюанс, який часто пропускають. Модуль ЧПУ в OpenCart 3 (catalog/controller/startup/seo_url.php) додає до адреси товару префікс категорій, якщо покупець прийшов з категорії. Один товар живе і за адресою /iphone, і за /desktops/mac/iphone. Canonical вказує на коротку версію, але в логах і беклінках трапляються обидві, тому довгі адреси теж мають вести на нову сторінку товару. Для мультимовного магазину повторіть запит для кожного language_id.
В OpenCart 4 структура таблиці змінилася. Замість query там поля key і value (наприклад, product_id і 48), тому в JOIN потрібно su.key = 'product_id' AND su.value = p.product_id. Шаблон адрес у четвертій версії залежить від налаштувань мови і модулів, тому звіряйте результат запиту з живим sitemap.
Стандартні шаблони адрес WooCommerce
За замовчуванням WooCommerce дає товарам адреси /product/назва-товару/, а категоріям /product-category/назва/ (документація WooCommerce). Якщо бази в налаштуваннях постійних посилань змінювали, відкрийте «Налаштування → Постійні посилання» і запишіть поточні значення до переїзду.
Крок 2. Складіть карту редиректів
Карта є таблицею з двох колонок old_path,new_path, де кожен рядок відповідає одній старій адресі. Відсортуйте її за кліками з Search Console, щоб угорі опинилися адреси, які приносять найбільше трафіку. Ці рядки перевіряйте вручну.
| Стара сторінка | Куди вести | Чого не робити |
|---|---|---|
| Товар, який є на новому сайті | 301 на той самий товар | Вести на категорію чи пошук |
| Товар, знятий з продажу | 301 на найближчу категорію або модель-наступника; якщо заміни немає, 404 чи 410 | Масово вести на головну |
| Категорія | 301 на відповідну категорію | Склеювати різні категорії в одну без потреби |
| Кілька сторінок, об'єднаних в одну | 301 на нову об'єднану сторінку | Лишати старі адреси з 200 і дубльованим текстом |
| Фільтри, сортування, пагінація | Новий аналог, якщо він є і індексується | Переносити тисячі сміттєвих параметрів у карту |
Правило про головну взяте з документації Google. Багато старих URL, що ведуть на одну нерелевантну сторінку, плутають користувачів і можуть бути оброблені як soft 404. Для видаленого контенту Google прямо рекомендує 404 або 410.
Якщо магазин уже переїжджав, візьміть старі редиректи і перенаправте їх одразу на кінцеві адреси. Інакше вийде ланцюжок «дуже стара адреса, потім стара, потім нова».
Найбільше часу забирає саме зіставлення. Коли каталог має десятки тисяч товарів, кілька мов і кастомні модулі фільтрів, карту доводиться збирати скриптами по артикулах і перевіряти вибірково. Цю роботу ми робимо до запуску нового сайту, щоб в день перемикання вона вже була протестована. Якщо ви плануєте переїзд магазину, надішліть адресу і нову платформу, і ми оцінимо обсяг карти.
Крок 3. Пропишіть редиректи на сервері
Google рекомендує серверні постійні редиректи 301 або 308 (Redirects and Google Search). JavaScript-перехід Googlebot побачить лише після рендерингу, а рендеринг може не відбутися. Для тисяч адрес у nginx зручно використати модуль map і окремий файл з картою:
# у блоці http
map_hash_max_size 262144;
map_hash_bucket_size 256;
map $request_uri $redirect_to {
default "";
include /etc/nginx/redirects/shop.map;
}
# у блоці server нового сайту
if ($redirect_to) {
return 301 $redirect_to;
}
Файл shop.map містить по одному рядку на адресу:
/iphone /smartfony/apple-iphone-15;
/desktops/mac/iphone /smartfony/apple-iphone-15;
"/index.php?route=product/product&product_id=48" /smartfony/apple-iphone-15;
Кілька деталей, на яких спотикаються:
- Змінна
$request_uriмістить адресу разом з параметрами і в тому вигляді, як її надіслав браузер. Кириличні ЧПУ приходять у percent-encoding (/%D1%82%D0%BE%D0%B2%D0%B0%D1%80), тому і в карті вони мають бути закодовані. - За документацією nginx рядки в
mapпорівнюються без урахування регістру, аmap_hash_max_sizeза замовчуванням 2048. Для великої карти nginx при перевірці конфігурації попросить збільшити хеш-таблицю, тому значення вище підібрані з запасом. - Якщо новий сайт на Laravel або WordPress і доступу до nginx немає, карту можна покласти в таблицю бази і віддавати 301 з middleware чи плагіна. Головне, щоб це був саме HTTP 301, а не сторінка з JavaScript.
Google радить тримати редиректи якомога довше, загалом щонайменше рік. Для покупців зі старими закладками кращий варіант залишити їх назавжди.
Крок 4. Перевірте карту скриптом до запуску
Розгорніть новий сайт на тестовому домені з тими самими правилами редиректів і проженіть усю карту. Скрипт на Python перевіряє, що кожна стара адреса віддає 301 або 308, веде саме туди, куди записано в карті, і що цільова сторінка відповідає 200 без додаткового переходу:
import csv, sys
import requests
# python check_redirects.py redirects.csv https://staging.shop.ua
base = sys.argv[2].rstrip('/')
bad = 0
with open(sys.argv[1], encoding='utf-8-sig') as f:
for row in csv.DictReader(f):
old, expected = row['old_path'], base + row['new_path']
r = requests.get(base + old, allow_redirects=False, timeout=15)
loc = requests.compat.urljoin(base + old, r.headers.get('Location', ''))
problem = None
if r.status_code not in (301, 308):
problem = f'status {r.status_code}, expected 301'
elif loc != expected:
problem = f'leads to {loc}'
else:
final = requests.get(loc, allow_redirects=False, timeout=15)
if final.status_code != 200:
problem = f'target answers {final.status_code} (chain or error)'
if problem:
bad += 1
print(f'FAIL {old} -> {problem}')
print(f'done, problems: {bad}')
sys.exit(1 if bad else 0)
Скрипт знаходить 404, 302, ланцюжки і редиректи не туди. Запускайте його після кожної правки карти, доки в звіті не буде нуля проблем. Паралельно перевірте на нових сторінках self-referencing canonical, hreflang для мовних версій, відсутність noindex і той robots.txt, який має бути на продакшні.
Перші 30 днів після запуску
- Подайте в Search Console новий sitemap і залиште старий. Google пропонує стежити, як кількість проіндексованих старих адрес падає до нуля, а нових росте.
- Щодня дивіться 404 у логах і додавайте пропущені адреси в карту:
awk '$9 == 404 {print $7}' /var/log/nginx/access.log \ | sort | uniq -c | sort -rn | head -50 - Замініть внутрішні посилання, адреси в рекламних кампаніях, фідах Google Merchant і на маркетплейсах на нові URL, щоб покупці не проходили через редирект.
- Порівнюйте кліки по топ-сторінках з базою, яку ви зберегли до переїзду. Сторінка, що просіла сильніше за решту, зазвичай має проблему з редиректом або контентом.
Інструмент «Зміна адреси» в Search Console потрібен лише при зміні домену чи субдомену. Для нової CMS на тому самому домені він не використовується.
Типові помилки
- Карту складають тільки з sitemap і втрачають старі адреси з беклінками.
- Усі видалені товари ведуть на головну замість категорій.
- Тимчасовий 302 ставлять «на перший час» і забувають замінити.
- Одночасно міняють CMS, структуру каталогу, домен і дизайн. Google радить міняти одне за раз. Так простіше зрозуміти, що саме спричинило падіння.
- Видаляють редиректи через кілька місяців, коли «вже все проіндексувалося».
- Не переносять ЧПУ з префіксом категорій в OpenCart і втрачають частину посилань на товари.
Якщо ви ще вирішуєте, чи варто взагалі переходити, почніть зі статті про ознаки того, що магазин переріс OpenCart. Технічні пункти, які варто перевірити на новому сайті перед запуском, зібрані в SEO-чеклисті для OpenCart.
Часті запитання
Чи втрачає сторінка позиції через 301-редирект?
За документацією Google, 301 і 308 не призводять до втрати PageRank. Тимчасові коливання позицій можливі, поки Googlebot не пройде старі і нові адреси. Для середнього сайту це займає кілька тижнів.
Скільки тримати редиректи після міграції?
Google рекомендує щонайменше рік. Ми радимо не видаляти їх зовсім. Зовнішні посилання і закладки покупців живуть роками, а серверний map відповідає на такі запити без звернення до PHP.
Куди вести товари, яких більше немає?
На модель-наступника або найближчу категорію, якщо вони відповідають запиту покупця. Якщо релевантної заміни немає, сторінка має віддавати 404 або 410. Масовий редирект на головну Google може розцінити як soft 404.
Чи можна зробити редиректи плагіном замість nginx?
Можна, якщо плагін віддає справжній HTTP 301. Для кількох сотень адрес це нормальний варіант. Для десятків тисяч рядків серверний map працює швидше, бо запит не доходить до PHP.
Перевіримо ваш переїзд до запуску
Надішліть адресу поточного магазину і назву платформи, на яку переходите. Ми подивимось sitemap, структуру ЧПУ і беклінки та скажемо, скільки старих адрес потребує редиректів і які з них приносять найбільше органіки. Повний технічний SEO-аудит коштує від $200, деталі на сторінці цін.
Про міграцію і розробку нового магазину дивіться розділ SEO-оптимізація та інтернет-магазини. Написати нам можна через форму на сайті або в Telegram.