Клиент приходит с жалобой: каталог тормозит, фильтры грузятся по минуте, SEO-трафик падает из-за дублей страниц. Чаще всего причина в архитектуре: неправильно выбрана модель категорий, неэффективная пагинация или отсутствие кеширования. Разработка каталога товаров — центральная задача для интернет-магазина, и её решение может сэкономить до 40% бюджета на поддержку (снижение затрат на инфраструктуру — до 300 тыс. руб. в год). По статистике, 70% пользователей покидают сайт, если каталог загружается дольше 3 секунд, а правильно спроектированный каталог может увеличить конверсию на 20%. Наш опыт — 8 лет в разработке каталогов, более 50 реализованных проектов для интернет-магазинов с трафиком от 10 000 посетителей в день.
Каталог — центральный модуль интернет-магазина. От его архитектуры зависит скорость поиска, удобство навигации и SEO-трафик. Ошибки здесь самые дорогостоящие: они требуют миграции данных и переработки зависимых модулей. Правильно спроектированный каталог товаров позволяет обрабатывать до 500 000 позиций без потери производительности.
Какую модель категорий выбрать?
Дерево категорий хранится в БД. Два распространённых подхода: Adjacency List — каждая запись хранит parent_id. Простота записи, но выборка всего дерева требует рекурсивного CTE:
WITH RECURSIVE category_tree AS ( SELECT id, name, parent_id, 0 AS depth FROM categories WHERE parent_id IS NULL UNION ALL SELECT c.id, c.name, c.parent_id, ct.depth + 1 FROM categories c JOIN category_tree ct ON c.parent_id = ct.id ) SELECT * FROM category_tree ORDER BY depth, name; Nested Sets (MPTT) — каждая запись хранит lft и rgt значения. Выборка поддерева: WHERE lft BETWEEN :parent_lft AND :parent_rgt — один запрос без рекурсии. Запись сложнее: при добавлении узла обновляются все правые соседи. Пакет kalnoy/nestedset для Laravel.
Closure Table — отдельная таблица всех предок-потомок пар. Самый гибкий, занимает больше места. Для сложных операций с деревом (перемещение поддеревьев) оптимален.
| Подход | Скорость выборки | Скорость записи | Память |
|---|---|---|---|
| Adjacency List | Низкая (рекурсия) | Высокая | Мало |
| Nested Sets | Высокая | Низкая | Умеренно |
| Closure Table | Высокая | Средняя | Много |
Рекомендация: для каталогов до 10 000 категорий Adjacency List с кешированием дерева в Redis — достаточно. MPTT — при частых выборках поддеревьев без кеша.
Как правильно реализовать атрибуты товаров?
Товары разных категорий имеют разные наборы атрибутов. Три подхода:
- Таблица с фиксированными колонками:
products.color,products.size,products.weight. Работает только при однородном ассортименте. Добавление нового атрибута — ALTER TABLE, миграция, деплой. - EAV (Entity-Attribute-Value): гибко, но медленно при JOIN-ах. Для фильтрации по атрибутам нужен Elasticsearch или денормализованный индекс.
- JSONB-колонка в PostgreSQL:
ALTER TABLE products ADD COLUMN attributes JSONB; CREATE INDEX ON products USING GIN (attributes); Компромиссный вариант: гибкость EAV, но без избыточных JOIN-ов. Как указано в PostgreSQL документации, JSONB-колонки обеспечивают гибкость EAV без избыточных JOIN-ов. Подходит для каталогов до 500 000 товаров.
Варианты товара: Parent-Child
Товар с вариантами (цвет × размер) — распространённая задача. Два паттерна:
- Simple SKU: каждая комбинация — отдельная запись в
products. Просто, но сложно управлять родительской карточкой. - Parent-Child: родительский товар типа 'variable' и дочерние 'variant' с конкретными комбинациями атрибутов.
products (id, type, parent_id, sku, name, price, stock) -- type: 'simple' | 'variable' | 'variant' -- variant: parent_id → variable product При отображении карточки товара загружаем родителя + все его варианты. Пользователь выбирает комбинацию атрибутов → находим соответствующий variant → обновляем цену, фото, наличие. Для матрицы вариантов используйте объект, индексированный по ID атрибута.
Структура URL и SEO
URL категорий — критично для SEO. Три варианта:
- Плоский:
/catalog/noutbuki— просто, теряет контекст иерархии. - Иерархический:
/catalog/elektronika/kompyutery/noutbuki— лучше для SEO, сложнее при перемещении категории. - Гибридный:
/noutbuki-c142— читаемый slug + уникальный ID (устойчив к переименованиям).
Для фильтрованных страниц: /noutbuki?brand=apple&ram=16 с canonical на /noutbuki или отдельные SEO-страницы для популярных комбинаций (/noutbuki-apple-16gb как статичная страница-агрегатор). Schema.org: ItemList на страницах категорий с ListItem для каждого товара в листинге.
Пагинация и бесконечная прокрутка
Offset-пагинация: LIMIT 48 OFFSET 144. Работает, но при глубоких страницах (OFFSET 10000) PostgreSQL всё равно читает 10048 строк. Решение — keyset pagination:
SELECT * FROM products WHERE (sort_value, id) > (:last_sort_value, :last_id) ORDER BY sort_value, id LIMIT 48; Keyset pagination мгновенна при любой глубине, но не поддерживает переход на произвольную страницу.
| Тип пагинации | Производительность | Поддержка произвольной страницы | SEO |
|---|---|---|---|
| Offset | Падает на глубине | Да | Частично |
| Keyset | Высокая | Нет | Лучше (noindex) |
| Бесконечная прокрутка | Высокая | Нет | Плохо |
Для мобайла — бесконечная прокрутка с IntersectionObserver, для десктопа с SEO-приоритетом — классическая пагинация (поисковики лучше индексируют страницы с явными номерами).
Управление каталогом в CMS
Административный интерфейс каталога:
- Bulk editing: выделить 50 товаров → изменить категорию/статус/цену.
- Импорт из CSV/XLSX: маппинг колонок, предпросмотр с ошибками, фоновая загрузка через queue.
- Drag-and-drop сортировка категорий: визуальное дерево с возможностью перетаскивания.
- Управление атрибутами: добавить атрибут в категорию — он появится на формах редактирования всех товаров категории.
Для bulk-импорта: Laravel Jobs + Horizon. Файл загружается в S3, задача берётся из очереди, парсится построчно (через league/csv или PhpSpreadsheet), товары вставляются batch-ами по 100 записей.
Как ускорить каталог с помощью кеширования?
Страницы каталога — основная нагрузка на БД. Стратегия кеширования:
| Уровень | Что кешируем | TTL |
|---|---|---|
| Redis | Дерево категорий | 1 час, сброс при изменении |
| Redis | Листинг с фильтрами | 5–15 минут |
| CDN (Cloudflare) | HTML страниц категорий | 5 минут, stale-while-revalidate |
| Браузер | Статика (изображения, JS, CSS) | immutable |
При изменении товара сбрасываем кеш только тех страниц, где он присутствует. Cache tags в Laravel: Cache::tags(['category:electronics'])->flush(). Оптимальная конфигурация кеширования может снизить время загрузки страницы с 3 до 0.5 секунд. Кеширование позволяет сократить расходы на серверы до 200 000 руб. в год.
Для каталогов с высокой динамикой (частые изменения цен или остатков) стоит сократить TTL листинга до 1-2 минут и использовать invalidate по тегу. Для статичных каталогов — увеличить TTL до часа.
Сроки
- Базовый каталог (категории, список товаров, карточка, пагинация): 2–3 недели.
- С вариантами, EAV-атрибутами, импортом и кешированием: 4–7 недель.
- Интеграция с Elasticsearch для поиска и фильтрации добавляет 2–3 недели.
Что входит в работу
- Подготовка технического задания с детальной моделью данных.
- Проектирование и реализация иерархии категорий, атрибутов и вариантов.
- Разработка SEO-оптимизированной структуры URL и Schema.org-разметки.
- Настройка пагинации и кеширования.
- Интеграция с административной панелью для управления ассортиментом.
- Документирование кода и API, обучение команды, передача доступов.
- Гарантийная поддержка в течение 30 дней после сдачи.
Если хотите оценить объём работ для вашего проекта, свяжитесь с нами — мы проанализируем текущий каталог и предложим решение. Получите бесплатный аудит вашего каталога: оставьте заявку на консультацию инженера. Наш специалист свяжется с вами в течение дня.
Типичные ошибки при проектировании каталога
- Использование offset-пагинации для каталогов >10 000 товаров — приводит к тормозам на глубоких страницах.
- EAV без индексации и кеша — убивает производительность при фильтрации.
- Отсутствие canonical на фильтрованных страницах — плодит дубли и снижает ранжирование.
- Плоский URL без защиты от переименований — теряется SEO-вес.
Эти ошибки увеличивают время загрузки на 40% и снижают конверсию. Избежать их поможет грамотное проектирование с учётом современных практик.







