Rowdy
Site and admin panel for a newly opened barbershop, in three languages.

A barbershop that had just opened, where my girlfriend had started working. Same shape of problem as the first one I'd built a year earlier — a new business with no web presence, needing somewhere to send people and a way to keep it current without calling me. This one served a mixed-language clientele, so the site had to work in Ukrainian, Russian and English.
Building the second one meant I already knew which parts of the first had been wrong. Some of that I fixed; some of it I traded away again, for different reasons.
The site had to be handed over, not maintained. Prices, barbers and gallery photos all change on their own schedule, and a shop that has to ask a developer to update a price will simply stop updating it. Everything the customer sees needed to be editable from an admin panel by someone who has never seen a deployment — and it needed to be editable in three languages without tripling the work of using it.
Next.js for the public site and the admin, with next-intl for locale routing, NextUI, Tailwind and Redux, deployed on Vercel. A NestJS API on Cloud Run, MongoDB through Mongoose, and Google Cloud Storage for gallery images uploaded through the admin. Bookings open the shop's existing Altegio widget.
Locale in the URL
Three languages, each on its own path. On the barbershop site I'd built the year before, the equivalent choice — which location you were viewing — lived in client state, which meant nothing was linkable and search engines only ever saw one version. Putting the locale in the path instead means every language is a real address that can be shared and indexed.
Object storage this time
On the previous build, gallery images uploaded to the API server's own filesystem, which meant they didn't survive a redeploy onto fresh storage. Here they go to Google Cloud Storage, so the images outlive any particular instance and the API stays stateless. Same feature, one lesson later.
Ship the admin as part of the deliverable
The shop needed to own its own content from day one. Services, barbers and the gallery are all editable through the admin, which is more work at build time and none afterwards — the alternative is being the person who edits a price list forever.
Hand off booking rather than build it
Appointments go through Altegio, which the shop already ran its calendar in. A second booking system would have been a second source of truth the staff had no reason to trust.
The second barbershop site I built, a year after the first. Most of what changed between them came from what the first one got wrong.
MongoDB was chosen for its free tier, not for its shape. The data here is thoroughly relational — barbers, services and prices with obvious foreign keys — and a document store was the wrong model for it. Managed Postgres on GCP was expensive, I didn't know Supabase existed yet, and Atlas had a free tier, so that decided it. It works, but the reason it works has nothing to do with the reason I picked it.
Translations are managed as files, so adding a language or fixing a phrase is a developer's job even though everything else about the site isn't. The admin covers content and stops at copy, which is a seam the owners can feel.
Redux again, and again for a site with almost no client state. That was the tool I reached for by default at the time rather than a judgement about this project.
Let's talk
Open to full stack roles, remote or on-site in Varna. The fastest way to reach me is email or Telegram.
