Harmal — luxury jewellery e-commerce and its admin panel
A trilingual e-commerce site for a Baku brand selling handmade jewellery and natural silk kelaghayi. Built full-stack on Next.js: an editorial storefront, a lifestyle journal and an admin panel the brand runs itself — for products, collections, journal articles and incoming enquiries.
Visit live site- Client
- Harmal — handmade jewellery and silk kelaghayi brand, Baku
- Role
- Full-stack development — every layer of the project: trilingual frontend architecture, Next.js route handlers, the Prisma/SQLite data model, JWT-based admin authentication, the image upload flow, performance work and the VPS deployment (PM2 + Nginx).
- Stack
- Next.js · TypeScript · Prisma · SQLite · Tailwind CSS
- Date
- August 2026
- Languages
- 3 — AZ / EN / RU
- Lighthouse (mobile)
- 93
- Lighthouse (desktop)
- 93
Selling luxury jewellery online presents two separate problems. The first is trust: the customer has to pay a four-figure sum for something they have only seen as a photograph on a screen. A standard e-commerce template — dense product grids, discount badges, “buy now” buttons — does not build that trust; it actively cheapens the brand. The second problem starts the day the site goes live. Collections change with the season, prices follow the gold rate, the journal needs new articles. If every one of those changes has to go through a developer, the site is stale within a few months — in practice the brand simply stops updating it.
- Trilingual interface — Azerbaijani, English and Russian, each at its own URL
- Collection showcase with product categories
- Editorial sections telling the origin story of the brand
- Customer testimonials section
- Journal (blog) section with dated articles
- Frequently asked questions section
- "The Harmal Standard" section explaining quality principles
- A live search field in the header
- A dedicated journal page — articles filter by Culture, Guide and Style
- Dedicated shop, contact and FAQ pages
- A parallax-driven origin story for the brand
- Admin panel: create, edit and delete products and collections
- Journal articles written and published from the admin panel
- Enquiries and orders from the site listed inside the panel
- Product and journal images uploaded straight from the panel
- JWT-protected admin login — panel routes are closed at middleware level
- Frontend
- Next.js App Router. Each language (AZ / EN / RU) lives on its own route; the storefront, shop, journal, contact and FAQ pages are rendered on the server. Images go through `next/image`, the origin story unfolds with parallax, and the header carries a live search field.
- Backend
- There is no separate API server — the backend is a set of route handlers inside the same Next.js app. Products, collections, journal articles and enquiries all pass through them. Frontend and backend share the same TypeScript types, so a renamed field surfaces as a build error rather than as a bug a customer finds first.
- Database
- Prisma + SQLite. The schema is declared in `schema.prisma` and changes ship as migrations, while the database itself lives as a single file on the server's own disk. Products, collections, journal articles and enquiries all sit in that one schema.
- Authentication
- A hand-rolled JWT flow: on a successful login the token is signed server-side and written into an `httpOnly` cookie. Everything under `/admin` is checked both at middleware level and inside each individual route handler.
- Media
- Product and journal images are uploaded from the admin panel and written to the VPS disk; on the site they are served through `next/image` at the size the screen actually needs.
- Infrastructure
- The Next.js server process runs on a VPS under PM2, which handles restarts and brings the app back up after a crash. Nginx sits in front as a reverse proxy: SSL, compression and static-file caching are resolved there.
Why Next.js?
A jewellery site is image-heavy, runs in three languages and needs its own management panel. Next.js brings all three into a single application: pages are served as ready HTML from the server, and the admin routes live inside the same project. With a classic SPA plus a separate API, the same work would have meant two repositories, two deployments and one set of type definitions written twice.
Image optimisation
Product photography is the heaviest part of the site. With `next/image` every image is served at the size the screen actually needs and in a modern format (WebP), so a phone never downloads a desktop-sized image. On a luxury brand the photography itself cannot be degraded, so the saving is taken out of dimensions, not out of quality.
Three languages on separate routes, tied together with hreflang
Each language lives at its own URL, so search engines index three separate pages — which is what lets the brand reach Azerbaijani- and Russian-speaking customers alike. But separate URLs alone are not enough: the engine also has to know these three pages are translations of the same content. That is what the hreflang links and the x-default marker on every page are for. Without them the three languages get indexed as rivals and the brand ends up competing with itself.
Structured data explains the brand to machines
The page carries four separate JSON-LD blocks. Rather than leaving a search engine or an AI model to infer what the brand sells, where it is based and what it publishes, it states all of it outright. That matters especially for a luxury brand, because the short description in a search result is often a customer's first contact with it.
The journal is part of the sales funnel
Someone reading about kelaghayi culture, someone looking for a diamond-buying guide and someone after everyday styling advice are three different people. The articles are split along exactly those three categories because each one arrives from a different search and leads to a different collection. The journal here is not a content section — it is the door into the shop pages.
Why SQLite and not Postgres?
Harmal's data profile is specific: hundreds of products and journal articles, a handful of writes a day, and thousands of reads against them. On that profile SQLite reads from the same machine with no network hop — there is no separate database server to query. Postgres would have added neither speed nor capability here; only another process, another chunk of memory and another point of failure. The Prisma schema stays in place, so if the shop grows to the point of needing many concurrent writes, changing the database is a migration a few lines long. Choosing technology for today's load is cheaper than paying up front for tomorrow's maybe.
Without a panel the site is stale in three months
Seasons change the collection, a rising gold price changes the prices, the journal needs new articles. Making those edits in code means the brand waits for a developer's free evening every time — and in practice it does not wait, it simply stops updating and the storefront freezes in last season. So products, collections and journal articles are managed from the admin panel: the brand maintains its own shop window, and I maintain the system underneath it.
The session token lives in an httpOnly cookie
The admin panel is the door to the brand's entire product catalogue and to its customer enquiries. Had the token been kept in `localStorage`, any XSS hole anywhere on the site could read it. An `httpOnly` cookie is invisible to JavaScript, and `SameSite` stops the token from riding along on requests issued by another site. The check itself is not only in middleware but inside every route handler: hiding an interface is not protection, only appearance — anyone who knows the API URL never sees the interface at all.
Why a VPS instead of Vercel?
Two parts of this project depend on a persistent disk: the SQLite file and the product images uploaded through the admin panel. In a serverless environment the file system is ephemeral — a written file survives until the next deployment, often only until the next request. Choosing Vercel would therefore have meant bolting on a paid service for the database and another one for image storage. On a VPS both sit right next to the application. SQLite, on-disk image storage and the VPS are not three independent decisions — they are three faces of the same one.
PM2 and Nginx divide the work
The Next.js server process cannot keep itself alive: it has to come back after a crash and start on its own when the server reboots — that is PM2's job. Nginx stands in front and takes on the work that does not belong to the app: TLS termination, compression, static-file caching. Without that split it would be running the application and doing cryptography on every request — meaning a product page would queue behind a certificate operation.
The site has been live in three languages since August 2026. PageSpeed Insights measures performance at 93 on both mobile and desktop, with an SEO score of 100 — for a storefront built around product photography, that is the image sizing and static generation doing their job. Day-to-day content belongs to the brand: products, collections, prices and journal articles are changed from the admin panel, with no developer involved in routine updates.
- Is Harmal built on a ready-made platform such as Shopify or WooCommerce?
- No. The site was written from scratch in Next.js and TypeScript, with its own database (Prisma + SQLite), its own backend routes and its own admin panel. There is no monthly subscription, no theme ceiling and no plugin dependency.
- Who adds the products and the journal articles?
- The brand does. Products, collections and journal articles are created, edited and deleted from the admin panel, and images are uploaded there too. No developer is involved in ordinary content changes.
- Who wrote the backend?
- Every layer of the project — the frontend, the Next.js route handlers, the Prisma data model, the admin panel, the authentication and the server deployment — was written by Nurlan Qadirov. Harmal is a frontend and a full-stack reference at the same time.
- How many languages does the site run in?
- Three: Azerbaijani, English and Russian. Each language lives at its own URL and is linked to the others with hreflang, so all three are independent pages as far as search engines are concerned.
- Where is the site hosted?
- On its own VPS. The Next.js server process runs under PM2 with Nginx in front as a reverse proxy. That choice exists so the SQLite database and the images uploaded through the panel stay on a persistent disk.
Tell me about your project — I usually reply the same day.
Nurlan Qadirov — Baku, Azerbaijan