The website of a Dubai luxury watch brand, built to show the collection and bring in customers, in English and Arabic. I delivered all three phases alone: the design documents, the Spring Boot API, the Vue admin console, the React front end and the deployment. Live at aldenroyston.com.
- 14tables, every text field in two languages
- 80+API endpoints across public and admin
- 15days early: phase three, due March 3, delivered February 16
- 1,200+organic search clicks in the first 28 days
The brief
The client asked for a site Google could find by the brand name, product pages with photos, video, specifications and prices, links to social media, the marketplaces and WhatsApp, and a way to list and unlist products. I was also to take care of the server and the domain. It had to open fast in the Middle East, and use video only where it would not slow a page down.
Before writing any code I wrote the architecture document (1,172 lines), the API design (1,836 lines) and the backend reference. The client preferred Java, Spring and MySQL, so that is what the backend uses.
System
The backend is Spring Boot 3 on Java 17, with MyBatis-Plus, Spring Security and JWT (24-hour tokens plus a refresh token), MySQL, Redis and MinIO. The front end is React 19 with TypeScript, Tailwind, i18next and Framer Motion; the admin console is Vue 3 with Element Plus. Public endpoints live under /api/v1 and admin ones under /api/v1/admin.
Fourteen tables: products with specifications, images, video, care instructions and tags; categories; home-page sections; contact links; files; users and roles. Redis caches product data, and if a transaction rolls back, a compensation step writes the old cache entry back.
Two languages, both directions
Where the translations live
Every translatable field has a column per language: name, name_en and name_ar, and specifications keep both the label and the value in each. The first design had the backend choose a language from the request headers. In phase two we changed it: the API returns every language at once, and the front end picks one, falling back when a translation is empty. Switching language needs no new request, and the cache never has to be split by language.
Right to left
Switching to Arabic sets the page direction to right-to-left and its language to Arabic, and the browser mirrors the whole layout from the same components. Only the footer needed its own direction class. Interface text lives in i18next, 117 lines of it in each language.
What changed across three phases
Live chat, built and then removed
Phase one included live customer chat over Spring STOMP. Messages the server logged as sent never arrived. After five rounds of narrowing it down, the cause turned out to be that user-specific destinations need a Principal bound to the WebSocket session; a handshake handler that gives each connection its own identity fixed it. Six days later, phase two dropped live chat in favour of WhatsApp and social links, and I removed it from the database up, two tables included.
A home page the client runs
Phase two split the home page into four configurable sections stored as JSON, and phase three let the admin pick exactly which products each one shows. Care instructions and a fixed table of nine specifications (model, water resistance, case, movement, strap, diameter, thickness, strap width, weight) became bilingual.
Phase three, early
Tags with their own colours replaced the fixed "Featured" badge, contact links got full management, and production moved to versioned images. It was planned to finish on March 3 and was delivered on February 16.
Deployment and result
Three Docker services (API, front end, admin) with Compose overrides per environment. In phase three I set up Gitea Actions, then moved to building versioned x86 images locally and pushing them to a private registry: fixed tags, never "latest".
In its first 28 days the site had 1,200+ organic search clicks and 4,300+ impressions (Search Console).
Looking back: the architecture document recommended server rendering for search, and I shipped a single-page app with a fixed title and description. That met the brief, being found by the brand name. Ranking individual products would need prerendered pages and a sitemap.
Timeline
- 2025.06.05Phase one: repository, database design, documents
- 2025.06.18Live chat delivery bug traced and fixed
- 2025.06.24Phase two: live chat removed; bilingual care and specifications
- 2025.06.27Home page split into four configurable sections
- 2026.02.09Phase three begins
- 2026.02.16Tags, product picking, contact management; delivered early
- 2026.02.27Production moves to versioned images