Engineering case study · 2025–2026
Public marketing site + admin CRM for a Dubai law firm. Cloudflare Pages + Workers + D1, trilingual (EN/AR/RU) with RTL-native Arabic typography, 49+ practice pages, and a lead pipeline that survives without an always-on backend.
UAE legal market has two persistent moats: domain authority on Arabic-keyword SERPs and same-business-day responsiveness on WhatsApp. Most firm sites in the segment treat the website as a brochure and the inbox as the CRM. That leaves money on the table on both sides: leads convert worse, and the website doesn't compound into an SEO asset.
Goal: build a single surface that doubles as a lead-magnet (49 practice-area pages targeting long-tail Arabic + English UAE legal queries) and a working pipeline (form capture → kanban → KYC → proposal → engagement) without committing to an always-on Postgres-and-Node stack the firm can't maintain.
Everything that can be static, is. Everything that needs state, runs on Cloudflare's edge primitives. No origin server.
INTERNET
│
┌──────────┴──────────┐
▼ ▼
noura-public admin/*
(static HTML) (CF Access gated)
Cloudflare Pages │
│ ▼
│ ┌──────────────────┐
│ │ Worker handlers │
│ │ /api/admin/* │
│ └────────┬─────────┘
│ │
▼ ▼
┌─────────────┐ ┌──────────────┐
│ Pages CDN │ │ D1 (SQL) │
│ static .html│ │ leads, │
│ + assets │ │ pipeline, │
└─────────────┘ │ audit, │
│ research idx │
└──────┬───────┘
▼
┌──────────────┐
│ R2 + KV │
│ KYC docs + │
│ rate limits │
└──────────────┘
dir="rtl"The lazy approach swaps direction and calls it done. The Arabic legal audience reads that as sloppy. The fix: Amiri for body, Cinzel for display, line-height 1.75, logical CSS properties (padding-inline-start) instead of physical, Eastern Arabic numerals via Intl.NumberFormat('ar-AE'), and Hijri date toggle on regulator-watch pages. Icons that imply direction (arrows, chevrons) get mirrored via scaleX(-1); icons that don't (clocks, briefcases) stay.
Three locales (EN, AR, RU) × 49 practice pages × 100+ insights = roughly 450 URLs that Google needs to recognize as language variants, not near-duplicates. Solution: per-locale canonical, full hreflang tag set on every page (including x-default), and locale-specific structured data with the correct inLanguage field. Verified via Search Console — zero language-confusion flags.
Lawyers don't reliably check three inboxes. Forms post to a Worker that writes to D1 and fans out to email + WhatsApp Business API + Slack simultaneously. If one channel fails, the others still alert. Every form has Turnstile + a honeypot field + KV-backed IP rate limit. Spam volume dropped from ~40/day to under 1/week.
Admin is hand-written HTML + ES modules, not a framework. Each page (inbox, pipeline, cockpit, research) is independently loadable — a bug in pipeline can't crash inbox. State machine pattern (loading | empty | error | data) is enforced explicitly on every async view. No silent failures.
| Surface | Before | After |
|---|---|---|
| Mobile contact CTAs | 3 overlapping floating buttons + bottom nav (5 contact entry points crowding the bottom of viewport) | Bottom nav (Call / Chat / Brief) + 1 chat FAB. WhatsApp lives inside the nav, not as a redundant pill. |
| Newsletter popup | Fired on first scroll, no exit-intent, no frequency cap, fired on the contact page (worst place to interrupt) | Triggers on (70% scroll OR 60s OR exit-intent), 30-day dismiss cooldown, suppressed on contact / book / subscribe paths. |
| Admin URL hygiene | .html suffixes leaking the static-export origin |
Clean URLs via _redirects 200-rewrites; deep links survive reload. |
| Lead pipeline view | Silent empty cards on cockpit — couldn't tell broken from empty | Explicit four-state machine (loading skeleton, illustrated empty + CTA, error card with retry, data view). |
Metrics are measured, not aspirational. Lighthouse on the live site at 375×812 mobile.
admin. subdomain in month two, not month nine.