← Back to Work

Zan — Pressed Flowers (freelance)

Bilingual EN / AR (LTR + RTL) landing site and booking funnel for Zan, a custom pressed-flower brand. Built end-to-end as a freelance engagement on Next.js 15 + Payload CMS 3 with measurable wins on Core Web Vitals, WCAG AA, and per-locale SEO.

Role
Freelance — Solo full-stack engineer (design provided)
Published
March 2026
Locales
EN · AR
Direction
LTR + RTL
Funnel steps
5
WCAG
2.1 AA
Next.js 15React 19TypeScriptTailwind CSS v4Payload CMS 3PostgreSQLAWS S3LexicalRadix UInext-sitemapVitestPlaywright

Context

Zan is a small custom pressed-flower brand. The freelance brief was to build their entire digital surface end-to-end as a solo engineer: design and copy were provided by the client; frontend, backend, CMS, infrastructure, and QA were all mine. A CMS-driven bilingual landing site (EN / AR · LTR + RTL) on top of Payload CMS 3, plus a conversion-critical booking funnel for ordering custom flower frames. One stakeholder, one production deadline, no engineering team to lean on — every architectural call had to be defensible on its own.

The frame for the engagement: this is a brand site that has to look effortless and load effortlessly, in two languages, with editor autonomy for everything except the funnel itself.

Problem

Three workstreams ran in parallel, each with its own failure mode:

  1. Landing site. Editors compose the homepage and content pages from typed blocks (Hero, Steps, Stats, Gallery, FAQ, CallToAction, MediaBlock, Form, Banner, Quote, Code, ArchiveBlock, RelatedPosts). If the blocks don't compose cleanly across LTR and RTL, the site becomes uneditable in Arabic.
  2. Booking funnel. Five-step purchase flow (Frame → Date → Information → Payment → Confirmed). It opens as a modal over any page, has to be screen-reader-correct, keyboard-complete, and not contribute to initial bundle weight on landing.
  3. Performance + SEO. A custom-product brand with limited paid acquisition needs organic search to work. That puts CWV and per-locale SEO on the critical path, not as a nice-to-have.

Architecture

One canonical URL per locale. A small custom middleware redirects //en, blocks /[locale]/home from existing as a duplicate of /[locale], and skips _next / api / admin / files. That gets rid of the most common bilingual-site SEO bug — two URLs ranking against each other for the same content.

Multi-language (EN / AR · LTR + RTL)

A bilingual brand site is two failure surfaces: the technical one (routing, fonts, direction) and the editorial one (whether the admin actually feels native in the second language). I treated them as one problem — and the answer is Payload's built-in localization, not a parallel i18n library.

One source of truth: payload.config.ts. Locales are declared once on the Payload config:

localization: {
  locales: [
    { code: 'en', label: { en: 'English', ar: 'الإنجليزية' } },
    { code: 'ar', label: { en: 'Arabic',  ar: 'العربية'   }, rtl: true },
  ],
  defaultLocale: 'en',
  fallback: true,
}

That single block does most of the heavy lifting:

Wiring it to Next.js. The frontend layer is thin:

Performance

The landing page exists to get someone to open the booking modal. Anything that delays first paint or competes with the hero image is a regression in the only metric that matters for this site.

Accessibility (WCAG 2.1 AA, audited)

A booking funnel that fails a screen-reader test fails the business — half of accessible-checkout work is dialog hygiene, the other half is making sure no interactive control is unreachable by keyboard. Concrete things I built in, not retrofitted:

SEO (per-locale)

This is a brand site with no paid acquisition baseline; organic has to work in both languages.

Booking funnel — engineering notes

The funnel is the conversion moment, so it's the part that gets the most discipline.

Outcome

← All case studies