Case study · 2026

Agadir Morocco Tours — Multilingual Tour Booking Platform

Four-language tour site (incl. Arabic RTL) with an owner dashboard, availability calendar and WhatsApp booking requests.

  • Next.js 16
  • TypeScript
  • PostgreSQL
  • Prisma 7
  • Zod
  • Tailwind CSS v4
  • sharp
  • Cloudinary
  • Vitest
Agadir Morocco Tours website homepage

Context

A small operator selling excursions, day trips and airport transfers. Customers are international; the owner confirms bookings personally on WhatsApp.

Constraints

  • English, French, Arabic (right-to-left) and Spanish
  • One non-technical owner manages all content
  • No online payment — a deliberate business choice the UI must state honestly
  • Minimal dependencies for long-term maintainability

System design

 Visitor (/en /fr /ar /es)                    Owner (/admin)
        │                                          │
┌───────▼─────────────────────┐     ┌──────────────▼──────────────┐
│ Public site (SSG/ISR)       │     │ Dashboard (Server Actions)  │
│ activities · pages · FAQ    │     │ activities · translations   │
│ reviews · availability      │     │ photos · slots · reviews    │
└───────┬─────────────────────┘     └──────────────┬──────────────┘
        │  booking panel                           │ requireAdmin()
        │  date → time → party size                │ DB sessions, scrypt
        ▼                                          ▼
 WhatsApp deep link             ┌────────────────────────────────┐
 (pre-filled, visitor's         │ PostgreSQL (Prisma 7)          │
  language + reply language)    │ Activity ─< ActivityTranslation│
        │                       │ Activity ─< AvailabilitySlot   │
        ▼                       │ Review, Page, FaqItem (+ i18n) │
 Owner confirms by reply        └────────────────────────────────┘
                                 Uploads → sharp (decode, WebP,
                                 strip EXIF, blur) → Cloudinary / S3

Key decisions

  • Translation rows, not JSON blobs. Facts (price, duration) live on the parent; text lives in *Translation rows keyed by locale. Adding a language needs no migration.
  • Incomplete translations are hidden, not faked. An activity missing Arabic simply isn't listed on the Arabic site — no English text under <html lang="ar">.
  • Localized slugs, Arabic kept in Arabic script; a slug from another language redirects to the right one.
  • Structural RTL with CSS logical properties and a dedicated Arabic type pairing.
  • WhatsApp instead of payments — matches how the owner already works; the UI says "request", never "booked".

Security

  • Opaque 256-bit session tokens, only SHA-256 stored; scrypt password hashing
  • Rate-limited sign-in with constant-time checks against account enumeration
  • Uploads decoded and re-encoded (EXIF/GPS stripped), never trusted by MIME type
  • No stored HTML rendered — rich text parsed to a typed tree; CSP and security headers

Results

  • Live at agadirmoroccotours.com in four languages
  • Owner runs content, availability and reviews without developer help
  • Vitest + Testing Library coverage on the booking flow

More projects

  • Dubai Haus website homepage
    2025–26Case study

    Dubai Haus — Real-Estate Platform, SEO & Growth

    Bilingual property platform built from scratch and grown through SEO — 2,200+ synced projects and 6.8× more organic clicks in 12 months.

    • Next.js
    • PostgreSQL
    • Prisma
    • Redis
    • +8
    Read case study
  • Saadiyat Marina website homepage
    2026Case study

    Saadiyat Marina — Bilingual Property Publication

    German-first Next.js publication on Abu Dhabi's Saadiyat Island — 54 URLs with a complete hreflang cluster, built in 7 working days.

    • Next.js 15
    • TypeScript
    • JSON-LD
    • hreflang
    • +3
    Read case study
Rehan Khan face Picture

Let's work
together

Version
2026 © Edition
Local time
Get in touch