Skip to content

Blueprint: Мультигород (Multicity)

Когда применять

Один сайт обслуживает несколько городов: «сайт для Краснодара и Сочи», региональные цены/телефоны, SEO-страницы под города. Фирменный типовой модуль студии.

Не применять при: единственном городе «на вырост» (добавить потом дешевле, чем таскать сложность), полностью разных сайтах по городам (это мультисайт).

Ключевое решение №1: схема URL

Выбрать с клиентом до реализации, менять потом дорого:

СхемаПлюсыМинусы
Путь /msk/uslugi (по умолчанию)Один домен, простой деплой и SSLПридётся аккуратно с корнем без города
Поддомены msk.site.ruКлассика локального SEOwildcard-DNS/SSL, куки между поддоменами

Blueprint описывает вариант «путь»; для поддоменов меняется только резолвер (Route::domain) — остальное идентично.

Состав решения

  • Модель City + миграция + сидер (города — из сидера, не руками)
  • Trait BelongsToCity (FK + scope forCity)
  • Middleware ResolveCity: город из первого сегмента URL → container/context
  • Trait HasDeclensions либо json-поле склонений города
  • Filament: CityResource, у городозависимых ресурсов — фильтр/колонка города
  • Blade: компонент выбора города (модалка со списком)
  • Тесты городов и подмены контента

Схема данных

cities

СтолбецТипИндексCast / примечание
ididPK
namestring«Краснодар»
slugstringuniquekrasnodar, сегмент URL
declensionsjsoncast array: gen/dat/prep — «в Краснодаре», «из Краснодара»
phonestring nullableгородской телефон (fallback на общий из settings)
addressstring nullable
is_defaultboolean default falseгород по умолчанию
is_activeboolean default trueindex
sort_ordersmallint default 0

Городозависимые сущности: city_id FK (BelongsToCity) — когда контент свой на город; если различаются только подстановки в текстах (город, телефон) — FK не нужен, достаточно переменных в шаблонах (specs/seo).

Правила реализации

  • ResolveCity middleware: сегмент → City (кеш списка городов тегом cities), неизвестный слаг → 404, без сегмента → default-город (301 на канонический URL с городом — решить по SEO-стратегии и зафиксировать).
  • Текущий город — через container binding (app(CurrentCity::class)) или контекст; не через session для SEO-страниц: поисковик должен получать контент по URL, не по куке. Кука — только для запоминания выбора и предложения переключиться.
  • Роуты: группа с префиксом {city:slug} + ->name() как обычно; хелпер/расширение route() не городить — передавать город параметром, Blade-компонент ссылок.
  • Склонения: {city}, {city_gen}, {city_prep} в SEO-шаблонах и текстах — из declensions; заполняются в сидере/админке (Filament: collapsed-секция из трёх TextInput).
  • hreflang не нужен (язык один), а вот canonical на каждый город — свой URL; сквозные страницы без городской специфики (политика конфиденциальности) — вне городской группы.
  • Sitemap: генерировать на все активные города × городозависимые страницы.
  • Кеш публичных страниц — ключ/тег обязательно включает город.

Пакеты

Без специальных: middleware + trait + json-склонения. Morpher/petrovich — только если города добавляются динамически и склонять руками некому.

Шаги

  1. Миграция + модель City + сидер городов со склонениями.
  2. Middleware ResolveCity + binding текущего города + 404/redirect-логика.
  3. Городская группа роутов; вынести вне её общие страницы.
  4. Подстановки в SEO-шаблоны и Blade (телефон/адрес города с fallback).
  5. BelongsToCity для городозависимых моделей (если есть).
  6. Компонент выбора города + кука предпочтения.
  7. Filament: CityResource; фильтр города в зависимых ресурсах.
  8. Тесты: каждый активный город → 200 и правильные подстановки; неизвестный город → 404; redirect-логика корня; sitemap множится на города; кеш не мешает городам.

Чеклист приёмки

  • [ ] Один и тот же URL всегда отдаёт один и тот же город (никакой session-магии)
  • [ ] Склонения корректны во всех SEO-шаблонах (проверить каждую форму)
  • [ ] Телефон/адрес города с fallback на общие настройки
  • [ ] Кеш страниц городозависимый (нет «Сочи показал телефон Краснодара»)
  • [ ] Sitemap и canonical по городам корректны
  • [ ] Добавление нового города = запись в админке + сидер склонений, без правки кода

Внутренняя база знаний студии