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, сжатие и кеширование статики решаются там.
Почему Next.js?
Сайт украшений насыщен изображениями, работает на трёх языках и одновременно нуждается в собственной панели управления. Next.js объединяет всё это в одном приложении: страницы отдаются с сервера готовым HTML, а маршруты админки живут внутри того же проекта. В связке «классическое SPA плюс отдельное API» та же работа означала бы два репозитория, два деплоя и одни и те же типы, написанные дважды.
Оптимизация изображений
Фотографии товаров — самая тяжёлая часть сайта. Через `next/image` каждое изображение отдаётся в том размере, который реально нужен экрану, и в современном формате (WebP) — телефон не скачивает картинку размером под десктоп. У люксового бренда качество съёмки понижать нельзя, поэтому выигрыш берётся из размеров, а не из качества.
Три языка на отдельных маршрутах, связанные через hreflang
Каждый язык живёт по своему адресу, поэтому поисковые системы индексируют три отдельные страницы — именно это позволяет бренду доходить и до азербайджаноязычных, и до русскоязычных покупателей. Но отдельного адреса мало: система должна ещё знать, что эти три страницы — переводы одного содержания. Для этого на каждой странице есть ссылки hreflang и указатель x-default. Без них три языка индексируются как конкуренты и бренд фактически соревнуется сам с собой.
Структурированные данные объясняют бренд машинам
На странице четыре отдельных блока JSON-LD. Поисковой системе и AI-моделям не приходится догадываться, что бренд продаёт, где он находится и какие статьи публикует — всё сказано прямо. Для люксового бренда это особенно важно, потому что короткое описание в поисковой выдаче часто становится первым контактом клиента с ним.
Журнал — часть канала продаж
Человек, читающий о культуре кялагаи, человек, ищущий гид по выбору бриллианта, и человек, которому нужен совет по повседневному стилю — это три разных человека. Статьи разделены именно на эти три категории, потому что каждая приходит из своего поиска и ведёт к своей коллекции. Журнал здесь не раздел с контентом, а вход на страницы магазина.
Почему SQLite, а не Postgres?
Профиль данных Harmal конкретен: сотни товаров и статей, несколько операций записи в день и тысячи чтений против них. На таком профиле SQLite читает с той же машины без сетевого перехода — отдельного сервера базы для запроса просто нет. Postgres здесь не дал бы ни скорости, ни новых возможностей: только ещё один процесс, ещё память и ещё одну точку отказа. Схема Prisma при этом остаётся на месте — если магазин вырастет и потребуется много одновременных записей, смена базы это миграция в несколько строк. Выбирать технологию под сегодняшнюю нагрузку дешевле, чем платить вперёд за завтрашнее «возможно».
Без панели сайт устаревает за три месяца
Меняется сезон — меняется коллекция, дорожает золото — меняются цены, в журнал нужны новые статьи. Вносить эти правки в код значит, что бренд каждый раз ждёт свободного вечера разработчика — а на практике он не ждёт, он просто перестаёт обновлять сайт, и витрина замерзает в прошлом сезоне. Поэтому товары, коллекции и журнал управляются из админ-панели: бренд сам держит свою витрину, а я держу систему под ней.
Токен сессии лежит в httpOnly cookie
Админ-панель — это дверь ко всей товарной базе бренда и к заявкам клиентов. Если бы токен хранился в `localStorage`, его могла бы прочитать любая XSS-дыра на сайте. `httpOnly` cookie не видна из JavaScript, а `SameSite` не даёт токену прицепиться к запросу, отправленному с другого сайта. Сама проверка стоит не только в middleware, но и внутри каждого route handler'а: спрятать интерфейс — не защита, а видимость, ведь тот, кто знает адрес API, интерфейс вообще не открывает.
Почему VPS, а не Vercel?
Две части этого проекта опираются на постоянный диск: файл SQLite и изображения товаров, загруженные через админку. В serverless-среде файловая система временная — записанный файл живёт до следующего деплоя, а часто и до следующего запроса. То есть при выборе Vercel пришлось бы подключать отдельный платный сервис под базу и ещё один под хранение изображений. На VPS и то и другое лежит рядом с приложением. SQLite, хранение изображений на диске и VPS — не три независимых решения, а три стороны одного.
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