Harmal — интернет-магазин люксовых украшений и его админ-панель

Трёхъязычный интернет-магазин для бакинского бренда украшений ручной работы и натуральных шелковых кялагаи. Собран как full-stack на Next.js: витрина в редакционном стиле, раздел журнала и админ-панель, которой бренд управляет сам — товары, коллекции, статьи журнала и входящие заявки.

Открыть сайт
Обзор
Клиент
Harmal — бренд украшений ручной работы и шелковых кялагаи, Баку
Роль
Full-stack разработка — все слои проекта: трёхъязычная архитектура фронтенда, route handler'ы Next.js, модель данных на Prisma/SQLite, аутентификация админки на JWT, загрузка изображений, производительность и деплой на VPS (PM2 + Nginx).
Стек
Next.js · TypeScript · Prisma · SQLite · Tailwind CSS
Дата
август 2026
Языки
3 — AZ / EN / RU
Lighthouse (мобильный)
93
Lighthouse (десктоп)
93
Задача

В онлайн-продаже люксовых украшений две отдельные проблемы. Первая — доверие: клиент должен заплатить четырёхзначную сумму за вещь, которую видел только на фотографии. Обычный шаблон интернет-магазина — плотная сетка товаров, ярлыки скидок, кнопки «купить быстро» — это доверие не создаёт, а наоборот удешевляет бренд. Вторая проблема начинается в день запуска. Коллекция меняется по сезону, цена зависит от курса золота, в журнал регулярно добавляются статьи. Если каждое такое изменение идёт через разработчика, сайт устаревает за несколько месяцев — на практике бренд просто перестаёт его обновлять.

Что входит в сайт
  • Трёхъязычный интерфейс — азербайджанский, английский и русский, каждый по своему адресу
  • Витрина коллекций с категориями товаров
  • Редакционные разделы, рассказывающие историю бренда
  • Раздел с отзывами клиентов
  • Журнал (блог) со статьями и датами публикации
  • Раздел частых вопросов
  • Раздел «Стандарт Harmal» с принципами качества
  • Живой поиск в шапке сайта
  • Отдельная страница журнала — статьи фильтруются по категориям «Культура», «Гид» и «Стиль»
  • Отдельные страницы магазина, контактов и часто задаваемых вопросов
  • История происхождения бренда, раскрывающаяся через параллакс
  • Админ-панель: добавление, редактирование и удаление товаров и коллекций
  • Написание и публикация статей журнала из админ-панели
  • Заявки и заказы с сайта в виде списка внутри панели
  • Загрузка изображений товаров и журнала прямо из панели
  • Вход в админку под защитой JWT — маршруты панели закрыты на уровне middleware
Архитектура
Фронтенд
Next.js App Router. Каждый язык (AZ / EN / RU) живёт по своему маршруту; витрина, магазин, журнал, контакты и FAQ рендерятся на сервере. Изображения проходят через `next/image`, история бренда раскрывается параллаксом, в шапке работает живой поиск.
Бэкенд
Отдельного API-сервера нет — бэкенд это route handler'ы внутри того же приложения Next.js. Через них проходят операции с товарами, коллекциями, журналом и заявками. Фронтенд и бэкенд используют одни и те же типы TypeScript, поэтому переименованное поле всплывает как ошибка сборки, а не как баг, который первым находит клиент.
База данных
Prisma + SQLite. Схема описана в файле `schema.prisma`, изменения идут миграциями, а сама база живёт единственным файлом на диске сервера. Товары, коллекции, статьи журнала и заявки находятся в одной схеме.
Аутентификация
Собственный поток JWT: при успешном входе токен подписывается на сервере и записывается в `httpOnly` cookie. Всё, что под `/admin`, проверяется и на уровне middleware, и внутри каждого route handler'а.
Медиа
Изображения товаров и журнала загружаются из админ-панели и записываются на диск VPS; на сайте они отдаются через `next/image` в том размере, который реально нужен экрану.
Инфраструктура
Серверный процесс Next.js работает на VPS под управлением PM2 — перезапуски и подъём после падения на нём. Впереди стоит Nginx как reverse proxy: SSL, сжатие и кеширование статики решаются там.
Технические решения
01

Почему Next.js?

Сайт украшений насыщен изображениями, работает на трёх языках и одновременно нуждается в собственной панели управления. Next.js объединяет всё это в одном приложении: страницы отдаются с сервера готовым HTML, а маршруты админки живут внутри того же проекта. В связке «классическое SPA плюс отдельное API» та же работа означала бы два репозитория, два деплоя и одни и те же типы, написанные дважды.

02

Оптимизация изображений

Фотографии товаров — самая тяжёлая часть сайта. Через `next/image` каждое изображение отдаётся в том размере, который реально нужен экрану, и в современном формате (WebP) — телефон не скачивает картинку размером под десктоп. У люксового бренда качество съёмки понижать нельзя, поэтому выигрыш берётся из размеров, а не из качества.

03

Три языка на отдельных маршрутах, связанные через hreflang

Каждый язык живёт по своему адресу, поэтому поисковые системы индексируют три отдельные страницы — именно это позволяет бренду доходить и до азербайджаноязычных, и до русскоязычных покупателей. Но отдельного адреса мало: система должна ещё знать, что эти три страницы — переводы одного содержания. Для этого на каждой странице есть ссылки hreflang и указатель x-default. Без них три языка индексируются как конкуренты и бренд фактически соревнуется сам с собой.

04

Структурированные данные объясняют бренд машинам

На странице четыре отдельных блока JSON-LD. Поисковой системе и AI-моделям не приходится догадываться, что бренд продаёт, где он находится и какие статьи публикует — всё сказано прямо. Для люксового бренда это особенно важно, потому что короткое описание в поисковой выдаче часто становится первым контактом клиента с ним.

05

Журнал — часть канала продаж

Человек, читающий о культуре кялагаи, человек, ищущий гид по выбору бриллианта, и человек, которому нужен совет по повседневному стилю — это три разных человека. Статьи разделены именно на эти три категории, потому что каждая приходит из своего поиска и ведёт к своей коллекции. Журнал здесь не раздел с контентом, а вход на страницы магазина.

06

Почему SQLite, а не Postgres?

Профиль данных Harmal конкретен: сотни товаров и статей, несколько операций записи в день и тысячи чтений против них. На таком профиле SQLite читает с той же машины без сетевого перехода — отдельного сервера базы для запроса просто нет. Postgres здесь не дал бы ни скорости, ни новых возможностей: только ещё один процесс, ещё память и ещё одну точку отказа. Схема Prisma при этом остаётся на месте — если магазин вырастет и потребуется много одновременных записей, смена базы это миграция в несколько строк. Выбирать технологию под сегодняшнюю нагрузку дешевле, чем платить вперёд за завтрашнее «возможно».

07

Без панели сайт устаревает за три месяца

Меняется сезон — меняется коллекция, дорожает золото — меняются цены, в журнал нужны новые статьи. Вносить эти правки в код значит, что бренд каждый раз ждёт свободного вечера разработчика — а на практике он не ждёт, он просто перестаёт обновлять сайт, и витрина замерзает в прошлом сезоне. Поэтому товары, коллекции и журнал управляются из админ-панели: бренд сам держит свою витрину, а я держу систему под ней.

08

Токен сессии лежит в httpOnly cookie

Админ-панель — это дверь ко всей товарной базе бренда и к заявкам клиентов. Если бы токен хранился в `localStorage`, его могла бы прочитать любая XSS-дыра на сайте. `httpOnly` cookie не видна из JavaScript, а `SameSite` не даёт токену прицепиться к запросу, отправленному с другого сайта. Сама проверка стоит не только в middleware, но и внутри каждого route handler'а: спрятать интерфейс — не защита, а видимость, ведь тот, кто знает адрес API, интерфейс вообще не открывает.

09

Почему VPS, а не Vercel?

Две части этого проекта опираются на постоянный диск: файл SQLite и изображения товаров, загруженные через админку. В serverless-среде файловая система временная — записанный файл живёт до следующего деплоя, а часто и до следующего запроса. То есть при выборе Vercel пришлось бы подключать отдельный платный сервис под базу и ещё один под хранение изображений. На VPS и то и другое лежит рядом с приложением. SQLite, хранение изображений на диске и VPS — не три независимых решения, а три стороны одного.

10

PM2 и Nginx делят работу

Серверный процесс Next.js не умеет жить сам: он должен подняться после падения и стартовать сам при перезагрузке сервера — это делает PM2. Nginx стоит впереди и берёт на себя работу, которая приложению не принадлежит: терминацию TLS, сжатие, кеширование статики. Без такого разделения он и выполнял бы приложение, и занимался криптографией на каждом запросе — то есть страница товара ждала бы в очереди за операцией с сертификатом.

Результат

Сайт работает на трёх языках с августа 2026 года. PageSpeed Insights показывает производительность 93 и на мобильных, и на десктопе, оценка SEO — 100. Для витрины, построенной вокруг фотографий товаров, это результат работы оптимизации изображений и статической генерации. Ежедневный контент в руках бренда: товары, коллекции, цены и статьи журнала меняются из админ-панели, разработчик для обычных обновлений не нужен.

Вопросы по этому проекту
Harmal построен на готовой платформе вроде Shopify или WooCommerce?
Нет. Сайт написан с нуля на Next.js и TypeScript — со своей базой данных (Prisma + SQLite), своими бэкенд-маршрутами и своей админ-панелью. Нет ни ежемесячной подписки, ни ограничений темы, ни зависимости от плагинов.
Кто добавляет товары и статьи журнала?
Сам бренд. Товары, коллекции и статьи создаются, редактируются и удаляются из админ-панели, изображения загружаются оттуда же. Для обычных изменений контента разработчик не нужен.
Кто написал бэкенд?
Все слои проекта — фронтенд, route handler'ы Next.js, модель данных на Prisma, админ-панель, аутентификация и деплой на сервере — написаны Нурланом Кадировым. Harmal одновременно и фронтенд-, и full-stack-пример работы.
На скольких языках работает сайт?
На трёх: азербайджанском, английском и русском. Каждый язык живёт по своему адресу и связан с остальными через hreflang, то есть для поисковых систем все три — самостоятельные страницы.
Где размещён сайт?
На собственном VPS. Серверный процесс Next.js работает под PM2, впереди стоит Nginx как reverse proxy. Этот выбор сделан ради того, чтобы база SQLite и загруженные через панель изображения оставались на постоянном диске.

Расскажите о проекте — обычно отвечаю в тот же день.

Nurlan Qadirov — Baku, Azerbaijan