Реализация маппинга 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 рабочих дня.