Налаштування мультидоменного сайту (різні домени по країнах)

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

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

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

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Налаштування мультидоменного сайту (різні домени по країнах)
Складна
~5 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • 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

Налаштування мультидоменного сайту (різні домени по країнах)

Коли бізнес працює в кількох країнах, кожна нерідко отримує власний домен: company.ru, company.kz, company.by, company.ua. Один кодовий движок обслуговує всі домени — різні мови, ціни, юридичні тексти, телефони. Складніше за поддиректорії, але дає максимальний локальний SEO-сигнал та повну ізоляцію контенту.

Конфігурація доменів

Центральна таблиця доменів зв'язує хост із налаштуваннями:

CREATE TABLE site_domains (
    id           SERIAL PRIMARY KEY,
    host         VARCHAR(253) UNIQUE NOT NULL, -- 'company.ru'
    country_code CHAR(2) NOT NULL,             -- 'RU', 'KZ', 'BY'
    locale       VARCHAR(10) NOT NULL,          -- 'ru', 'kk', 'be'
    currency     CHAR(3) NOT NULL,              -- 'RUB', 'KZT', 'BYN'
    timezone     VARCHAR(64) NOT NULL,
    is_primary   BOOLEAN DEFAULT false,
    is_active    BOOLEAN DEFAULT true,
    meta         JSONB DEFAULT '{}'
);

INSERT INTO site_domains VALUES
  (DEFAULT, 'company.ru',  'RU', 'ru', 'RUB', 'Europe/Moscow',    true,  true, '{}'),
  (DEFAULT, 'company.kz',  'KZ', 'kk', 'KZT', 'Asia/Almaty',      false, true, '{}'),
  (DEFAULT, 'company.by',  'BY', 'ru', 'BYN', 'Europe/Minsk',     false, true, '{}');

Middleware визначення домену

// app/Http/Middleware/ResolveSiteDomain.php
class ResolveSiteDomain
{
    public function handle(Request $request, Closure $next): Response
    {
        $host = $request->getHost(); // 'company.kz'

        $domain = SiteDomain::where('host', $host)
            ->where('is_active', true)
            ->first();

        if (!$domain) {
            // Невідомий домен — редирект на основний
            $primary = SiteDomain::where('is_primary', true)->firstOrFail();
            return redirect("https://{$primary->host}" . $request->getRequestUri(), 301);
        }

        app()->instance('site.domain', $domain);

        // Встановлюємо локаль та часовий пояс
        App::setLocale($domain->locale);
        Carbon::setlocale($domain->locale);
        date_default_timezone_set($domain->timezone);

        return $next($request);
    }
}

Мультитенантна конфігурація Laravel

// app/Providers/DomainServiceProvider.php
public function boot(): void
{
    $this->app->resolving('current.domain', function () {
        return app('site.domain');
    });

    // Переопределяємо mail from для кожного домену
    $this->app['events']->listen(MessageSending::class, function ($event) {
        $domain = app('site.domain');
        config([
            'mail.from.address' => "noreply@{$domain->host}",
            'mail.from.name'    => config('app.name') . ' ' . strtoupper($domain->country_code),
        ]);
    });
}

Зберігання контенту по доменах

-- Переводимі тексти привязані до домену
CREATE TABLE pages (
    id         SERIAL PRIMARY KEY,
    slug       VARCHAR(255) NOT NULL,
    domain_id  INTEGER REFERENCES site_domains(id),
    -- NULL в domain_id = загальний контент для всіх доменів
    UNIQUE (slug, domain_id)
);

CREATE TABLE page_translations (
    page_id    INTEGER REFERENCES pages(id),
    locale     VARCHAR(10) NOT NULL,
    title      TEXT,
    body       TEXT,
    meta_title TEXT,
    meta_desc  TEXT,
    PRIMARY KEY (page_id, locale)
);

Запит контенту з fallback на загальний:

public function getPage(string $slug): Page
{
    $domain = app('site.domain');

    // Спочатку ищемо сторінку, специфічну для домену
    $page = Page::where('slug', $slug)
        ->where('domain_id', $domain->id)
        ->first();

    // Fallback на загальну сторінку
    $page ??= Page::where('slug', $slug)
        ->whereNull('domain_id')
        ->firstOrFail();

    return $page;
}

Nginx: віртуальні хости для PHP-FPM

# /etc/nginx/sites-available/multisite.conf
server {
    listen 443 ssl http2;
    server_name company.ru company.kz company.by;

    ssl_certificate     /etc/letsencrypt/live/company.ru/fullchain.pem;
    # Wildcard сертифіката або multi-domain SAN

    root /var/www/company/public;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param HTTP_HOST $host; # Передаємо реальний хост у PHP
        include fastcgi_params;
    }
}

Якщо домени на різних серверах — додається балансувальник з заголовком X-Forwarded-Host, який Laravel читає через TrustProxies middleware.

SSL-сертифікати

Certbot із мультидоменним SAN:

certbot certonly --nginx \
  -d company.ru -d www.company.ru \
  -d company.kz -d www.company.kz \
  -d company.by -d www.company.by \
  --email [email protected] \
  --agree-tos

Альтернатива — wildcard *.company.ru + DNS challenge.

Crossdomain SEO

Кожен домен — самостійний сайт в очах Google. Для зв'язку між ними використовують hreflang в <head>:

<!-- На company.ru -->
<link rel="alternate" hreflang="ru-RU" href="https://company.ru/products/item-1" />
<link rel="alternate" hreflang="ru-KZ" href="https://company.kz/products/item-1" />
<link rel="alternate" hreflang="ru-BY" href="https://company.by/products/item-1" />

Генерація через хелпер:

function hreflangTags(string $path): string
{
    $domains = SiteDomain::where('is_active', true)->get();
    return $domains->map(fn($d) =>
        "<link rel=\"alternate\" hreflang=\"{$d->locale}-{$d->country_code}\" href=\"https://{$d->host}{$path}\" />"
    )->join("\n");
}

Проблема з куками та сесіями

При переході користувача між доменами сесія теряется — куки не передаються між різними доменами. Варіанти:

SSO через shared token: при кліку «Перейти на сайт для Казахстану» генерується одноразовий токен, користувач переадресується з ним, другий домен обмінює токен на сесію.

Загальне сховище сесій: Redis з одним і тим же SESSION_DOMAIN — але браузер все одно не передасть куку з .ru на .kz. Цей підхід працює лише для поддоменів.

Миграція при додаванні домену

Додавання нового домену займає менше години:

  1. Реєстрація домену, налаштування DNS A-записів — від хвилин до доби (propagation)
  2. Додання в site_domains — 2 хвилини
  3. Додання в SAN сертифіката (certbot --expand) — 5 хвилин
  4. Додання server_name в Nginx — 1 хвилина
  5. Імпорт або створення контенту — залежить від обсягу

Мониторинг

Окрема health check сторінка /health на кожному домені. Uptime Robot або Checkly пінгує всі домени щохвилини, алерт при недоступності. Перевірка SSL expiry окремо — через ssl_certificate_expire метрику в Prometheus або сторонній сервіс.