Реализация автоматического перенаправления по региону на сайте

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

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Реализация автоматического перенаправления по региону на сайте
Простая
от 1 рабочего дня до 3 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Реализация автоматического перенаправления по региону на сайте

Автоматический редирект по региону — логичное продолжение GeoIP-определения. Пользователь открывает site.ru, система определяет его город и перенаправляет на site.ru/spb/ или spb.site.ru. Задача простая, но у неё есть несколько острых углов: петля редиректов, боты, пользователи из-за рубежа, кэширование на CDN.

Базовая логика редиректа

// app/Http/Controllers/RegionRedirectController.php
class RegionRedirectController
{
    public function __invoke(Request $request): RedirectResponse|Response
    {
        // Боты и поисковики не редиректим — они обходят конкретные URL
        if ($this->isCrawler($request->userAgent())) {
            return app(HomeController::class)->index($request);
        }

        // Если пользователь уже выбирал регион — уважаем его выбор
        if ($preferred = $request->cookie('region')) {
            return $this->redirectToRegion($preferred, $request);
        }

        // GeoIP определение
        $geo    = app(GeoIpService::class)->lookupCached($request->ip());
        $region = $this->matchRegion($geo['city'], $geo['region_name'], $geo['country_code']);

        // Редирект 302 (не 301 — регион может измениться)
        return $this->redirectToRegion($region->slug, $request)
            ->withCookie(cookie('region', $region->slug, 60 * 24 * 30)); // 30 дней
    }

    private function redirectToRegion(string $slug, Request $request): RedirectResponse
    {
        $path = $request->getPathInfo(); // '/' на главной
        return redirect("/{$slug}{$path}", 302);
    }

    private function isCrawler(?string $ua): bool
    {
        if (!$ua) return false;
        $bots = ['Googlebot', 'bingbot', 'YandexBot', 'Baiduspider',
                 'DuckDuckBot', 'Slurp', 'facebookexternalhit'];
        foreach ($bots as $bot) {
            if (str_contains($ua, $bot)) return true;
        }
        return false;
    }
}

Защита от петли редиректов

Самая распространённая ошибка — редирект срабатывает на уже региональных URL. Middleware защищает:

// app/Http/Middleware/SkipRegionRedirect.php
public function handle(Request $request, Closure $next): Response
{
    // URL уже содержит slug региона — пропускаем
    $regions  = Region::pluck('slug')->toArray();
    $firstSeg = explode('/', trim($request->getPathInfo(), '/'))[0] ?? '';

    if (in_array($firstSeg, $regions, true)) {
        return $next($request);
    }

    // Корневой URL — выполняем редирект
    return app(RegionRedirectController::class)($request);
}

Применяется только к корневому маршруту:

Route::get('/', RegionRedirectController::class)
    ->middleware(SkipRegionRedirect::class);

Сопоставление города с регионом сайта

GeoIP возвращает название города на английском — нужно сопоставить с внутренним slug:

private function matchRegion(
    ?string $city,
    ?string $region,
    ?string $country
): Region
{
    // Сначала по городу
    if ($city) {
        $match = RegionGeoMapping::where('city_en', $city)->first();
        if ($match) return $match->region;
    }

    // По области/региону
    if ($region) {
        $match = RegionGeoMapping::where('region_en', $region)->first();
        if ($match) return $match->region;
    }

    // По стране — базовый регион для страны
    if ($country) {
        $match = Region::where('country_code', $country)
            ->where('is_country_default', true)
            ->first();
        if ($match) return $match;
    }

    // Глобальный дефолт
    return Region::where('is_default', true)->firstOrFail();
}

Таблица маппинга:

CREATE TABLE region_geo_mappings (
    id         SERIAL PRIMARY KEY,
    region_id  INTEGER REFERENCES regions(id),
    city_en    VARCHAR(255),   -- 'Saint Petersburg'
    region_en  VARCHAR(255),   -- 'Saint Petersburg'
    country    CHAR(2)         -- 'RU'
);

Кнопка смены региона

Пользователь должен иметь возможность изменить автоопределённый регион. При смене — обновляем куку:

// AJAX endpoint
Route::post('/set-region/{slug}', function (string $slug, Request $request) {
    abort_unless(Region::where('slug', $slug)->exists(), 404);

    $redirectTo = $request->input('redirect_to', "/{$slug}/");

    return response()->json(['redirect' => $redirectTo])
        ->withCookie(cookie('region', $slug, 60 * 24 * 365)); // 1 год
});

На фронтенде — при клике на регион в шапке или попапе:

async function setRegion(slug) {
    const res = await fetch(`/set-region/${slug}`, {
        method: 'POST',
        headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name=csrf-token]').content },
        body: JSON.stringify({ redirect_to: `/${slug}/` })
    });
    const { redirect } = await res.json();
    window.location.href = redirect;
}

CDN и кэширование

CDN (Cloudflare, Fastly) кэширует страницы. Если CDN кэширует корень /, все пользователи получат одинаковый редирект — тот, что был закэширован первым. Варианты:

Вариант A: исключить / из кэша CDN (Cache-Control: no-store на корневом URL).

Вариант B: использовать Cloudflare Workers для редиректа на edge:

// Cloudflare Worker
addEventListener('fetch', event => {
    event.respondWith(handleRequest(event.request));
});

async function handleRequest(request) {
    const url    = new URL(request.url);
    const cookie = request.headers.get('Cookie') || '';
    const region = cookie.match(/region=([a-z]+)/)?.[1];

    if (url.pathname === '/' && !region) {
        const country = request.cf?.country || 'RU';
        const slug    = countryToRegion[country] || 'msk';
        return Response.redirect(`${url.origin}/${slug}/`, 302);
    }
    return fetch(request);
}

Вариант C: редирект на клиенте через JS — самый простой, но пользователь видит мигание страницы.

Поведение для иностранных пользователей

Если GeoIP вернул страну, которой нет в списке регионов, — нужно решение:

  • Показать попап «Выберите регион» без автоматического редиректа
  • Перенаправить на дефолтный регион (msk) с попапом «Вы из другой страны?»
  • Перенаправить на отдельную страницу site.ru/international/

Решение зависит от доли иностранных пользователей в аудитории.

Отладка

Для тестирования редиректов с разных IP-адресов удобен query-параметр в dev-окружении:

if (app()->isLocal() && $testIp = $request->query('test_ip')) {
    $geo = app(GeoIpService::class)->lookup($testIp);
}

https://site.dev/?test_ip=77.109.0.1 — симулирует пользователя с конкретным IP.