Интернет-магазины

Самописные интернет-магазины на чистом стеке — без CMS, фреймворков и тяжёлой инфраструктуры. Полный цикл: каталог, корзина, онлайн-оплата, админ-панель, SEO и аналитика.

Интернет-магазин

Вектор пола — магазин напольных покрытий

РольЕдинственный разработчик — full-stack: архитектура данных, бэкенд, фронтенд, админ-панель, SEO и деплой
СтекPHP 8 · SQLite (PDO) · HTML · CSS · JavaScript — без CMS, фреймворков, npm и Composer

Действующий интернет-магазин напольных покрытий: ламинат, кварцвинил и SPC, инженерная и паркетная доска — у заказчика два салона, в Москве и Красногорске. В каталоге около 7 600 активных товаров: 3 300 позиций ламината, 3 900 кварцвинила и SPC, плюс паркетная и инженерная доска. Витрина написана на чистом PHP, без CMS и фреймворков, работает на дешёвом shared-хостинге и не требует ни одной сборки. Ключевое инженерное решение — двухслойное хранилище: канонический JSON на 17,5 МБ и производный SQLite-кеш, из которого витрина и читает. Заказы идут не через онлайн-оплату, а заявками в Telegram — так устроен процесс продаж у заказчика.

Архитектура данных

Ключевая идея: products.json — источник истины, catalog.sqlite — производный read-кеш. Витрина больше не декодирует 17,5 МБ JSON на каждый запрос, а обращается к готовой базе через PDO.

  • Админка пишет в JSON, а база пересобирается один раз за запрос через register_shutdown_function — это критично для импорта, который сохраняет товары в цикле и иначе запускал бы пересборку на каждой строке.
  • Пересборка атомарная: пишем в catalog.sqlite.tmp и подменяем через rename. При ошибке временный файл удаляется, а старая база остаётся живой — живые запросы никогда не попадают на полузаписанную базу.
  • Две таблицы: products (22 колонки, флаги active/in_stock/popular/promo и поле search_text = lower(name + brand) для регистронезависимого поиска по кириллице) и product_facets с развёрнутыми значениями фильтров по каждому товару. Пять индексов на товарах, три на фасетах.
  • Устойчивость к рассинхрону схемы: запросы к новым колонкам обёрнуты в try/catch. Если PHP залит раньше пересборки базы, ни витрина, ни админка не падают в 500 — деградируют мягко, пустым блоком или откатом на JSON.

Каталог и фильтры

Около 7 600 активных товаров в 4 наполненных категориях из 9; остальные показывают состояние «Раздел наполняется» вместо пустой страницы.

  • Фасетные фильтры сконфигурированы под реальные характеристики каждой категории: бренд, класс истираемости, толщина, тип укладки, порода, покрытие. Логика — ИЛИ внутри фасета и И между фасетами, у каждого значения счётчик товаров.
  • Диапазон цены с подсказкой реальных границ по категории, фильтр «только в наличии», сортировка по цене и серверная пагинация по 24 товара.
  • Авто-сабмит фильтров: на десктопе — сразу по клику, в мобильной шторке — по кнопке «Показать», цена — по Enter.
  • Поиск по названию и бренду с мультисловными запросами: каждое слово должно найтись в нормализованном search_text.
  • Данные каталога отнормализованы: без нулевых цен и дублей slug, бренды проставлены, ключи характеристик канонизированы.

Карточка товара и заказ

У напольных покрытий своя специфика: товар продаётся упаковками, а покупатель считает в квадратных метрах.

  • Калькулятор площади: вводишь метраж — считает количество упаковок с округлением вверх, фактическую площадь и итоговую сумму. Первый расчёт отрисован на сервере, поэтому карточка осмысленна и с отключённым JavaScript.
  • Галерея с переключением фото, характеристики таблицей, кнопки «В корзину» и «Купить в один клик», ссылки «поделиться» в WhatsApp и Telegram и копирование адреса.
  • Корзина на клиенте (localStorage) с пересчётом упаковок по каждой позиции; оформление отправляет заявку, а не платёж — состав заказа и сумма уходят в Telegram текстом.
  • Двухслойная заглушка для фото: пустой images — фолбэк на сервере, битая ссылка — onerror на клиенте.

Заявки и уведомления

  • Все формы — консультация, покупка в один клик, корзина, доставка, сотрудничество с дизайнерами — идут через один обработчик в Telegram-бота, с пометкой источника заявки.
  • Раздельные таймауты на коннект и на всю операцию (5 и 15 с). При сбое API заявка дописывается в закрытый лог data/leads-failed.log — лид не теряется, даже если Telegram недоступен.
  • Подписка на акции пишется в JSON под flock, с дедупликацией по e-mail: повторная отправка обновляет имя, телефон и дату, а не плодит записи.
  • На клиенте — единая инициализация форм, которая читает эндпоинт и текст успеха из data-атрибутов; маска телефона и валидация переиспользуются всеми формами.

Кастомная админ-панель

Полностью самописная, за логином на PHP-сессиях.

  • Каталог: CRUD товаров, серверные поиск и пагинация, вкладки «Список», «Популярные», «Акционные» — галки в карточке товара управляют каруселями на главной, а пустая секция просто не рендерится.
  • Импорт и экспорт XLSX через собственные читалку и писалку на ZipArchive и ручном XML — без Composer и внешних библиотек. Экспорт разворачивает все ключи характеристик в отдельные столбцы, шаблон отдаётся со строкой-примером, фиксированные и дополнительные столбцы подкрашены разным цветом.
  • Импорт делает upsert: сначала по slug, затем по артикулу; чинит артикулы вида «1.0», которые Excel и Google Таблицы делают из целых чисел, и умеет подтягивать фото по ссылке.
  • Загрузка изображений: центральный кроп в квадрат 800×800 и конвертация в WebP на сервере (GD), приём и файлом, и по URL, с проверкой MIME.
  • Подписки: список с новыми сверху, удаление через POST с подтверждением и PRG-редиректом, выгрузка в CSV (с BOM для Excel) и XLSX.
  • Защита входа: арифметическая капча, блокировка на 5 минут после 5 неудачных попыток, восстановление пароля в Telegram и на e-mail. Ручная пересборка базы — отдельной страницей.

SEO

  • Динамический sitemap.xml прямо из SQLite: статика, только непустые категории и все активные товары с lastmod из даты обновления — на сегодня порядка 7 600 URL.
  • Schema.org / JSON-LD: Product + Offer с priceValidUntil, itemCondition и динамической доступностью на карточках, BreadcrumbList на карточках, категориях и индексе каталога.
  • Осознанное управление индексацией: чистая пагинация индексируется с self-canonical и rel prev/next, а комбинации фильтров и сортировок уходят в noindex,follow — чтобы не плодить дубли.
  • SEO-title и description задаются по каждому товару в админке, с фолбэком на шаблон из названия. Человекочитаемые URL /catalog/{категория}/{slug}/ через mod_rewrite, кириллица транслитерируется в slug с гарантией уникальности.
  • Внутренний поиск и корзина закрыты от индексации, robots.txt и sitemap согласованы между собой.

Производительность и хостинг

Скорость вытянута архитектурой хранилища, а не инфраструктурой: ни CDN, ни кеш-слоя, ни фоновых демонов — дешёвый shared-хостинг Beget и заливка по rsync.

  • Отказ от разбора 17,5 МБ JSON на каждый запрос в пользу индексированных SQL-выборок — главный выигрыш по времени ответа.
  • Все ~7 800 товарных фото приведены к единому WebP 800×800, логотипы брендов — к WebP 320×180. Пайплайн подготовки — локальные скрипты на Python/PIL с обрезкой белых полей и ограничением апскейла.
  • Preload и fetchpriority на hero-изображении (LCP), ленивая загрузка остальных картинок, явные width/height — нулевой сдвиг макета.
  • Gzip и заголовки кеширования статики в .htaccess, cache-busting ассетов через ?v=N.
  • Аналитика — Яндекс.Метрика с webvisor и clickmap.

Безопасность и надёжность

  • Папка /data/ с товарным JSON и базой подписчиков закрыта и в .htaccess (RewriteRule с [F]), и в robots.txt; config.php с токеном бота запрещён к прямому вызову.
  • В /uploads/ отключено исполнение PHP и разрешены только изображения; служебные файлы админки закрыты через FilesMatch, и каждый дополнительно сам проверяет, что его вызвали не напрямую.
  • Осмысленные 404 для несуществующих товаров и категорий: проверяется не только slug, но и совпадение категории в URL с категорией самого товара.

Чем интересен проект

  • Каталог на 7 600 товаров работает на shared-хостинге без CMS, без MySQL, без npm и Composer — за счёт продуманного хранилища, а не мощностей.
  • JSON как источник истины и SQLite как производный read-кеш: редкая запись, частое чтение — компромисс, который для этого проекта оказался точнее полноценной СУБД.
  • Инженерные детали, которые видно только в эксплуатации: атомарная пересборка базы, одна пересборка на запрос вместо тысяч при импорте, мягкая деградация при рассинхроне схемы, фолбэк-лог для потерянных заявок.
  • Свои XLSX-читалка и писалка вместо тяжёлой библиотеки — заказчик ведёт ассортимент в Excel, и это работает без единой внешней зависимости.
  • Полный цикл одним человеком: архитектура данных → бэкенд → фронтенд → админка → пайплайн изображений → SEO → деплой.
Интернет-магазин

Wergrauf — магазин сантехники

РольЕдинственный разработчик — full-stack: архитектура, бэкенд, фронтенд, интеграции, SEO и деплой
СтекPHP · HTML · CSS · JavaScript — без фреймворков, CMS и базы данных

Заказчик сменил бренд и захотел поднять уровень площадки. Под новый бренд я с нуля собрал интернет-магазин сантехники на чистом стеке — без CMS, без фреймворков и без MySQL. Данные о товарах живут в JSON-файлах, которые автоматически синхронизируются с Google Таблицами. Полный цикл: каталог из 9 категорий, карточки товаров, корзина, оформление и онлайн-оплата, отзывы с модерацией, кастомная админ-панель, e-commerce-аналитика и SEO. Сайт держит 90–100 баллов PageSpeed Insights на десктопе и мобильных.

Архитектура

Намеренно лёгкая «безбазовая» архитектура: вместо CMS и SQL — файловое хранилище и интеграция с Google Sheets как удобной панелью наполнения.

  • Источник данных — Google Sheets API: одна таблица, отдельный лист на категорию. PHP-скрипт синхронизации тянет данные через API и сохраняет в JSON — контент-менеджер ведёт ассортимент в привычной таблице, на сайте всё обновляется по кнопке.
  • Локализация изображений: при синхронизации внешние фото товаров скачиваются на сервер, конвертируются в WebP и переименовываются по slug. Идемпотентно, с фолбэком при сбое.
  • Слой оверрайдов: поверх данных из таблицы накладываются ручные правки из админки (метатеги, скрытие товаров, порядок) — таблица остаётся источником истины, но любое поле можно переопределить локально.
  • Серверный рендеринг каталога для корректной внутренней перелинковки и SEO; клиентский JS отвечает только за фильтры и сортировку.

Ключевые функции

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

Корзина, заказ, оплата: корзина на стороне клиента (localStorage), оформление и покупка «в один клик», онлайн-оплата через Ozon СБП, страница статуса заказа с поллингом оплаты, уведомления о новых заказах в Telegram через бота.

Отзывы: форма с загрузкой до 4 фото и трёхуровневой антиспам-защитой (honeypot + проверка времени заполнения + арифметическая капча), хранение в JSON, модерация через админку.

Подписка на новинки с сохранением базы подписчиков и просмотром в админке.

Кастомная админ-панель

Полностью самописная, авторизация на PHP-сессиях.

  • Дашборд со статистикой по категориям.
  • Ручная синхронизация с Google Таблицами в один клик, с логом результатов.
  • Просмотр и редактирование товаров каждой категории, включая метатеги (title/description) для SEO.
  • Добавление ручных товаров поверх синхронизированных и редактирование контента главной страницы.
  • Модерация отзывов, просмотр подписчиков, лог синхронизаций.
  • Сервисные инструменты: пакетная оптимизация изображений, очистка системного мусора.

Производительность (PageSpeed Insights)

Сайт доведён до 90–100 баллов Performance на десктопе и мобильных.

  • Адаптивные размеры изображений: для каждого товарного фото при синхронизации генерируются производные размеры (карточка ~600px, превью ~160px) поверх оригинала для галереи — товарные изображения легче на 65–70%, превью под главным фото с ~65 КБ до ~2 КБ.
  • WebP везде — товары и логотипы (через <picture> с фолбэком).
  • Устранение Cumulative Layout Shift: явные width/height на всех изображениях.
  • Приоритизация загрузки: fetchpriority на главном изображении (LCP), lazy-load остального, preconnect к домену аналитики, defer на скриптах.
  • Оптимизация контраста, заголовков и шрифтов под мобильные.

SEO

  • Schema.org / JSON-LD: Product + Offer (динамическое наличие и priceValidUntil), BreadcrumbList на карточках, Organization + WebSite на главной.
  • Канонические URL, keyword-first title-теги, корректная иерархия заголовков, <main>-лендмарки, семантическая разметка.
  • Автогенерируемые sitemap.xml и robots.txt.
  • YML-фид для Яндекс.Директа (товарные кампании, смарт-баннеры), который сам обновляется после синхронизации каталога.
  • Accessibility: ARIA-метки, контраст по WCAG, доступная навигация.

Аналитика

Полная e-commerce-аналитика на Яндекс.Метрике: цели и dataLayer на ключевые действия (добавление в корзину, заказ, покупка в один клик, успешная оплата, подписка), защита от двойного срабатывания целей, webvisor, clickmap. Покупка отслеживается с защитой от дублей при поллинге оплаты.

Чем интересен проект

  • Нулевые зависимости от тяжёлой инфраструктуры: ни CMS, ни SQL, ни фреймворка — при этом полноценный магазин с оплатой, аналитикой и админкой.
  • Google Sheets как backend наполнения — нетривиальная, но удобная для заказчика интеграция.
  • Производительность как осознанный приоритет: оптимизация изображений встроена в пайплайн данных, а не прикручена постфактум.
  • Полный цикл одним человеком: архитектура, бэкенд, фронтенд, интеграции, SEO, деплой и поддержка.
Интернет-магазин

Hizberg — магазин сантехники

РольЕдинственный разработчик — вёрстка по макету заказчика, бэкенд, оплата, деплой
СтекPHP · HTML · CSS · JavaScript (jQuery) — без фреймворков, CMS и базы данных

Ранний коммерческий проект: интернет-магазин сантехники, собранный с нуля по макету заказчика. Сознательно простое «безбазовое» решение — контент захардкожен в страницах, товары и цены лежат в PHP-файлах, заказы — в файловой структуре без СУБД. При этом магазин закрывает полный цикл продажи: каталог, карточки товаров с выбором цвета и количества, корзина, оформление, онлайн-оплата, отслеживание статуса заказа, импорт отзывов с маркетплейсов и уведомления клиенту. Прагматичный MVP, которого бизнесу на тот момент хватало для реальных продаж.

Архитектура

Статичная файловая структура без СУБД и шаблонизатора: каждый товар — отдельная папка со своим index.php и изображениями, цены вынесены переменными в один общий PHP-файл, категории (смесители, душевые системы, комплектующие, аксессуары) — каталог из вложенных папок, общие блоки (шапка, меню, футер) подключаются инклюдами. Подход осознанно топорный, но прозрачный и надёжный для статичного ассортимента.

Каталог и товары

Карточка товара: галерея с зумом, выбор цвета переключением между вариантами, счётчик количества с пересчётом цены на лету, хлебные крошки с микроразметкой Schema.org. Корзина — на стороне клиента (localStorage), со скидочными купонами и бесплатной доставкой от пороговой суммы.

Заказы и оплата

Заказы хранятся в файловой структуре: на каждый создаётся папка со статусом, данными клиента и сгенерированной страницей заказа. Онлайн-оплата картой и через СБП по эквайрингу Тинькофф (платёжная сессия через API, редирект на оплату, авто-смена статуса при успешной оплате); альтернативно — по реквизитам (QR + PDF) или наличными. Клиент получает трек-код и страницу отслеживания статуса.

Админка

Предельно простая: единый лог всех заявок, где каждая строка снабжена ссылками-действиями «оплачен / выполнен / закрыт». Менеджер обрабатывает заказы прямо из лога — подтверждает оплату, меняет статус, закрывает. Ровно столько функций, сколько нужно для обработки потока, без СУБД и тяжёлого интерфейса.

Отзывы

Импорт отзывов с маркетплейсов (Ozon, Wildberries): парсер разбирает сохранённые страницы, отбирает отзывы с высокой оценкой, чистит и дедуплицирует, складывает в единый список с подгрузкой по мере прокрутки. Ссылки ведут на исходные карточки маркетплейсов.

Уведомления

При оформлении заказа клиент получает письмо и SMS с номером заказа и трек-кодом.

SEO

Уникальные title/description на страницах, человекочитаемые URL по структуре каталога, хлебные крошки с микроразметкой, sitemap.xml, robots.txt, favicon — базовый, но осмысленный технический минимум.

Чем интересен проект

Ранний этап «с нуля по макету, без CMS и фреймворков», собранный вручную примерно за неделю (ещё до эпохи AI-инструментов) — и при этом рабочий магазин с реальной онлайн-оплатой и обработкой заказов. Тот же заказчик позже сделал ребрендинг в Wergrauf уже на зрелой data-driven архитектуре — по двум проектам виден рост подхода.