Тимур ТурдыевНаписать

кейс · 2026

Магазин офисной мебели, 2022-2026

Веду prvoffice.ru с нуля четвёртый год: своя админка, своя витрина, три редизайна, три этапа поиска и массовое редактирование каталога.

LaravelLivewireManticoreimgproxyMariaDB

Проект ​

prvoffice.ru - магазин мебели для офиса. Веду его с 2022 года, с нуля: на старте не было ни админки, ни витрины, ни каталога. За четыре года проект пережил три редизайна, и каждый раз это был не перекрас кнопок, а смена того, как устроена витрина внутри.

Проект клиентский, поэтому ниже - про инженерные решения, а не про цифры и процессы.

Главная страница магазина: витрина на Laravel и Blade, серверный рендер

Как менялась витрина ​

Веб-архив хранит почти всю историю домена, по нему хорошо видно, откуда проект стартовал.

В декабре 2021 года на prvoffice.ru стояла чужая заготовка: телефон-заглушка +7 (123) 456-78-90, город Красноярск и открытая на бою панель отладки Tracy с девятью ошибками. С неё я и начинал, но в дело из неё не пошло ничего.

prvoffice.ru в декабре 2021: заготовка до меня, панель отладки внизу справа

Осенью 2022 года, пока я строил админку и каталог, на домене висела заглушка с логотипом. Магазин не открывали, пока не появилось что показать.

prvoffice.ru в октябре 2022: заглушка, пока идёт работа

Первая версия магазина, которая сохранилась в архиве целиком, датирована 2024 годом. У неё новый логотип «оснащение под ключ», каталог разделами в строку меню и слайдер на всю ширину.

prvoffice.ru в октябре 2024: первый дизайн, каталог в строке меню

В 2025 году каталог ушёл под одну кнопку, а поиск стал главным элементом шапки. Каталог вырос настолько, что меню из девяти разделов перестало помещаться в строку, и искать оказалось быстрее, чем листать.

prvoffice.ru в июле 2025: кнопка «Каталог» и поиск во всю шапку

Сейчас, в 2026 году, в каталоге больше 38 тысяч товаров. Шапка сохранила поиск и кнопку каталога, а главная превратилась в витрину разделов с подборками.

prvoffice.ru в октябре 2026: текущая витрина, больше 38 тысяч товаров

Все три редизайна сделаны на одной платформе и одной базе. Менялись шаблоны и то, как витрина собирает страницу, а админка и данные оставались на месте.

Два приложения, одна база ​

Админка и витрина - два отдельных Laravel-приложения, читающих одну базу напрямую, без API между ними. Решение спорное на вид, но осознанное: API здесь был бы слоем ради слоя, а так витрина получает данные одним запросом и не зависит от доступности админки. Граница проходит по правам на запись, а не по HTTP.

Так было не с первого дня. До осени 2024 года витрина жила в репозитории админки: один проект, два набора маршрутов. Когда витрина стала меняться чаще админки, я вынес её в отдельное приложение со своим деплоем, а базу оставил общей.

Админка собрана на Livewire 4 поверх AdminKit - готовой статической темы, без сборки фронтенда: интерфейс для небольшой команды контент-менеджеров не окупает build-цепочку. Витрина - Blade и Tailwind, серверный рендер.

Поиск: три этапа за четыре года ​

Поиск по каталогу менялся трижды, и это не метания, а разные этапы жизни проекта.

SQL LIKE - первый этап. Пока каталог был небольшим, поиска по подстроке в базе хватало: ноль инфраструктуры, один трейт на модели, результат предсказуемый. Проблемы начались, когда выросли и каталог, и ожидания: ни опечаток, ни ранжирования, ни быстрых фасетов из этого не выжать.

Meilisearch появился в декабре 2024 года через Laravel Scout. Быстро поднимается, релевантность хорошая из коробки, поиск с опечатками работает сразу. Scout дал готовую индексацию моделей, и на этом движке магазин прожил больше полутора лет.

Manticore стоит с августа 2026 года. Переезжал ради фасетов и управляемости: нужен был движок с SQL-интерфейсом, который живёт на том же сервере и которым можно управлять как базой, а не через HTTP-обёртку. Поисковый модуль написал заново, сверил выдачу со старым движком и только потом снёс путь через Scout. Сейчас Manticore отдаёт фасеты с живыми счётчиками, подсказки, исправление опечаток и поиск по артикулу.

Каталог кресел: фасеты с живыми счётчиками по брендам и группам, почти десять тысяч товаров в выдаче

Подсказки в поиске отдаются тем же слоем: пользователь видит товар с ценой и картинкой ещё до перехода в каталог.

Подсказки поиска: товары с артикулом и ценой прямо в выпадающем списке

Вывод, ради которого стоило это пройти: поисковый движок - не решение на всю жизнь. Он меняется вместе с каталогом, и переезд надо закладывать в архитектуру с самого начала, держа запросы за одним слоем.

Массовое редактирование каталога ​

Главный инструмент контент-менеджера - batch editor. Задача звучит просто: выбрать сотни товаров по условию и поменять им поле. На практике сложность в выборе.

Фильтр - рекурсивное дерево AND/OR: группы вкладываются друг в друга, каждое условие бьёт по атрибуту, опции, категории или полю товара. Дерево сериализуется в JSON, живёт в состоянии Livewire между перезагрузками и компилируется в Eloquent-запрос.

Конструктор фильтра в админке: условие по колонке товара и вложенная группа со своей логикой, 37 тысяч товаров в наборе

Выбранный набор дальше уходит в массовые операции: поле, действие, значение - и правка применяется ко всему, что попало в фильтр, а не к отмеченным вручную строкам.

Массовое изменение поля по фильтру: цена, действие и значение одним экраном

Сейчас в работе второе поколение редактора. Первое осталось на месте и работает: у контент-менеджера в панели переключатель между версиями, и новая версия обкатывается на реальных задачах, пока старая остаётся страховкой. Переписывать инструмент, которым пользуются каждый день, с обрывом - плохая идея.

Картинки ​

Ресайз был сделан на nginx с Lua: запрос за картинкой нужного размера попадал в скрипт, тот генерировал файл и отдавал. Работало, но жило внутри веб-сервера, и любая правка означала правку конфига nginx на бою.

Сейчас это imgproxy - готовый сервис, который я не стал писать сам. Он ничего не знает о приложении: получает пресет и имя файла в URL, отдаёт картинку нужного размера. Набор пресетов закрытый, произвольные размеры снаружи запросить нельзя. Кэш лежит не в сервисе, а в nginx через proxy_cache, так что повторный запрос до imgproxy не доходит. Заодно исчез класс проблем "картинки не отдаются, потому что кто-то тронул конфиг".

Регионы ​

Магазин работает по регионам через поддомены: 81 поддомен, по одному на регион из справочника ФИАС, со склонением названия в текстах. RegionMiddleware определяет регион по хосту и подставляет его в каждый запрос, дальше от него зависят цены и кэш категорий. Региональная разбивка уезжает и в выгрузки: фиды для Яндекса собираются с поддоменом региона в ссылках. Для пользователя это выглядит как "сайт знает, откуда я".

Переезд на новый сервер ​

В августе 2026 года магазин переехал на новый сервер. Раскатку описал в Ansible, выкладку - в Deployer с атомарными релизами, там же подняты Manticore и imgproxy. DNS переключил 23 августа 2026 года, витрина и админка поехали на том же стеке.

Что дальше ​

Каталог, поиск и инструменты редактирования достаточно зрелые, чтобы заняться тем, что реально двигает продажи, а не поддерживает работу магазина. Следующий шаг - переезд магазина на мою платформу Mercurio.

© 2014-2026 Тимур ТурдыевСайт и справочник работают на VPS от AdminVPS