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

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

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

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

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

Сейчас, в 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-запрос.

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

Сейчас в работе второе поколение редактора. Первое осталось на месте и работает: у контент-менеджера в панели переключатель между версиями, и новая версия обкатывается на реальных задачах, пока старая остаётся страховкой. Переписывать инструмент, которым пользуются каждый день, с обрывом - плохая идея.
Картинки
Ресайз был сделан на nginx с Lua: запрос за картинкой нужного размера попадал в скрипт, тот генерировал файл и отдавал. Работало, но жило внутри веб-сервера, и любая правка означала правку конфига nginx на бою.
Сейчас это imgproxy - готовый сервис, который я не стал писать сам. Он ничего не знает о приложении: получает пресет и имя файла в URL, отдаёт картинку нужного размера. Набор пресетов закрытый, произвольные размеры снаружи запросить нельзя. Кэш лежит не в сервисе, а в nginx через proxy_cache, так что повторный запрос до imgproxy не доходит. Заодно исчез класс проблем "картинки не отдаются, потому что кто-то тронул конфиг".
Регионы
Магазин работает по регионам через поддомены: 81 поддомен, по одному на регион из справочника ФИАС, со склонением названия в текстах. RegionMiddleware определяет регион по хосту и подставляет его в каждый запрос, дальше от него зависят цены и кэш категорий. Региональная разбивка уезжает и в выгрузки: фиды для Яндекса собираются с поддоменом региона в ссылках. Для пользователя это выглядит как "сайт знает, откуда я".
Переезд на новый сервер
В августе 2026 года магазин переехал на новый сервер. Раскатку описал в Ansible, выкладку - в Deployer с атомарными релизами, там же подняты Manticore и imgproxy. DNS переключил 23 августа 2026 года, витрина и админка поехали на том же стеке.
Что дальше
Каталог, поиск и инструменты редактирования достаточно зрелые, чтобы заняться тем, что реально двигает продажи, а не поддерживает работу магазина. Следующий шаг - переезд магазина на мою платформу Mercurio.