Реалізація маппінгу URL (старі → нові) та налаштування 301-редиректів при міграції

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.
Розробка та обслуговування будь-яких видів сайтів:
Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація маппінгу URL (старі → нові) та налаштування 301-редиректів при міграції
Середня
~2-3 робочих дні
Часті питання
Наші компетенції:
Етапи розробки
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація маппінгу URL (старих → нових) та налаштування 301-редиректів під час міграції

301-редиректи під час міграції — основний інструмент передачі SEO-ваги сторінок на новий сайт. Кожен втрачений URL без редиректу — це потенційно втрачений трафік та позиції у пошуку.

Створення таблиці URL-маппінгу

URL-маппінг ведеться в CSV-таблиці, яка стає єдиним джерелом істини:

old_url,new_url,status_code,priority,notes
/blog/2020/01/old-slug,/articles/old-slug,301,high,main page
/category/news,/blog/news,301,high,category page
/wp-content/uploads/img.jpg,/media/img.jpg,301,medium,media file
/contact-us,/contacts,301,high,page renamed
/product/old-name,/shop/new-name,301,high,product renamed
/old-promo-page,,410,low,deleted page

Статус 410 (Gone) для видалених сторінок сигналізує пошуковику про остаточне видалення — краще ніж 404 для сторінок, які не повернуться.

Автоматичний маппінг за slug

Якщо змінилася лише структура URL (додався/забрався префікс):

def generate_url_map(old_urls, url_transform_fn):
    mapping = []
    for old_url in old_urls:
        new_url = url_transform_fn(old_url)
        if old_url != new_url:
            mapping.append({'old': old_url, 'new': new_url, 'code': 301})
    return mapping

# Приклади трансформацій
def wp_to_flat(url):
    # /2020/01/post-slug → /articles/post-slug
    import re
    match = re.match(r'^/\d{4}/\d{2}/(.+)$', url)
    if match:
        return f"/articles/{match.group(1)}"
    return url

def add_lang_prefix(url, lang='ru'):
    # /page → /ru/page
    if not url.startswith(f'/{lang}/'):
        return f'/{lang}{url}'
    return url

Генерація nginx map

def generate_nginx_map(mapping_csv, output_file):
    import csv

    lines = ['# Auto-generated redirects', 'map $request_uri $redirect_target {']
    lines.append('    default "";')
    lines.append('    hostnames;')  # включити підтримку hostname patterns

    with open(mapping_csv) as f:
        reader = csv.DictReader(f)
        for row in reader:
            old = row['old_url'].rstrip('/')
            new = row['new_url']
            code = row.get('status_code', '301')

            if code == '410':
                # Для 410 використовуємо інший map
                continue

            # Основний URL
            lines.append(f'    "~^{re.escape(old)}$"  "{new}";')
            # З trailing slash
            if old != '/':
                lines.append(f'    "~^{re.escape(old)}/$"  "{new}";')

    lines.append('}')

    with open(output_file, 'w') as f:
        f.write('\n'.join(lines))

Конфігурація Nginx:

include /etc/nginx/redirect_map.conf;

server {
    listen 80;
    server_name site.com www.site.com;

    # Обробити редиректи
    if ($redirect_target != "") {
        return 301 $redirect_target;
    }

    # 410 для видалених сторінок
    location ~* ^/(old-promo|deleted-category|removed-product) {
        return 410;
    }

    # Універсальний редирект для невідомих старих шляхів
    # (обережно — може зломати новий контент)
    # try_files $uri $uri/ @legacy_redirect;
}

Генерація .htaccess для Apache

def generate_htaccess(mapping_csv, output_file):
    lines = [
        'RewriteEngine On',
        'RewriteBase /',
        ''
    ]

    with open(mapping_csv) as f:
        reader = csv.DictReader(f)
        for row in reader:
            old = row['old_url'].lstrip('/')
            new = row['new_url']
            code = row.get('status_code', '301')

            if code == '410':
                lines.append(f'RewriteRule ^{re.escape(old)}$ - [G,L]')
            else:
                lines.append(f'RewriteRule ^{re.escape(old)}$ {new} [R={code},L]')

    with open(output_file, 'w') as f:
        f.write('\n'.join(lines))

Перевірка покриття редиректів

import requests

def verify_redirects(mapping_csv, base_url):
    errors = []

    with open(mapping_csv) as f:
        reader = csv.DictReader(f)
        for row in reader:
            old_url = f"{base_url}{row['old_url']}"
            expected_new = row['new_url']
            expected_code = int(row.get('status_code', 301))

            # Перевірити лише редирект, не слідувати
            resp = requests.get(old_url, allow_redirects=False)

            if expected_code in (301, 302):
                if resp.status_code != expected_code:
                    errors.append(f"Expected {expected_code}, got {resp.status_code}: {old_url}")
                elif not resp.headers.get('Location', '').endswith(expected_new):
                    errors.append(f"Wrong redirect target: {old_url} → {resp.headers.get('Location')}, expected {expected_new}")
            elif expected_code == 410:
                if resp.status_code != 410:
                    errors.append(f"Expected 410, got {resp.status_code}: {old_url}")

    return errors

Краулінг старого сайту для повного покриття

Перед налаштуванням редиректів потрібен повний список URL:

# Screaming Frog експорт всіх URL
# або wget-краулінг
wget --spider --recursive --no-verbose --output-file=crawl.log \
  https://old-site.com 2>&1

grep -E "^--" crawl.log | awk '{print $3}' | sort -u > all_urls.txt
# Знайти URL з all_urls.txt, не покриті редиректами
with open('all_urls.txt') as f:
    crawled_urls = {line.strip() for line in f}

with open('mapping.csv') as f:
    mapped_old_urls = {row['old_url'] for row in csv.DictReader(f)}

uncovered = crawled_urls - mapped_old_urls
print(f"Uncovered URLs ({len(uncovered)}):")
for url in sorted(uncovered):
    print(f"  {url}")

GSC-моніторинг після запуску

Google Search Console → Coverage → Excluded → Crawled – currently not indexed

Якщо після міграції з'являється багато нових 404 — це пропущені редиректи. Сторінки з трафіком (з GSC Performance) мають бути покриті редиректами в порядку пріоритету.

Строк виконання

Створення маппінгу для сайту до 1000 URL, генерація конфіга nginx та перевірка — 2–3 робочих дні.