Skip to main content
Back to work
2024 — Present · Sole engineer · Lebanon-based clients

Multi-tenant eCommerce Storefront Platform

A custom React + Vite storefront platform powering multiple Lebanese retail brands — Garden State and LGT — from one codebase, with per-tenant branding, catalogue, analytics, and feature modules driven by a tenant config.

storefront · tenant runtime

/ tenant-config.json

Lebanon · LBP / USD

Live tenants

2

in production

Customer surfaces

30+

code-split pages

Codebase

1

one repo, many brands

Live tenants

01Context

Lebanese retail brands need full eCommerce storefronts — catalogue, cart, checkout, account, returns, promotions, content — but each brand has its own catalogue shape, branding, feature mix, and operating realities. Building one custom storefront per client doesn't scale: every fix, every checkout improvement, every analytics tweak has to be repeated across forks that drift apart over time.

I designed and built a single platform that solves this — one React + Vite codebase, per-tenant configuration injected at boot, separate deployments and CDNs per brand, and a CMS layer that lets each tenant manage their own content without engineering involvement.

02Challenge

The brief: a real, full storefront. Not a marketing page with "buy now" on it — a complete shopping system with accounts, ordering, returns, subscriptions, wishlist, and a content layer. Per-tenant branding, per-tenant catalogue, per-tenant analytics, per-tenant feature modules.

Constraints that shaped the architecture:

  • One codebase. Forking per client was a non-starter — it would have killed maintainability inside three tenants.
  • SPA performance, but every page chunk had to lazy-load so initial paint stayed cheap on Lebanese mobile networks.
  • Tenant features could diverge — LGT needed an "Occasions" gift-catalogue module that Garden State didn't — so the platform had to support per-tenant feature toggles without conditional code soup.
  • Content-driven pages had to be editable by non-engineers via a CMS layer, not hard-coded.

03Approach

  • React + Vite SPA with aggressive code-splitting — every customer-facing surface ships as its own chunk, so the initial bundle stays small and pages stream in on navigation.
  • Tenant runtime config. A tenant-config.json loads at boot and drives branding, theme tokens, analytics IDs (GTM / GA4), feature toggles, and API endpoints. No tenant-specific code in the bundle — everything flows from config.
  • Dedicated media CDN per tenant (e.g. files.lgt.company) for product imagery and assets, so each brand's media stays cleanly isolated and cacheable.
  • Full customer surface set — catalogue, product detail, cart, checkout, account, addresses, login / forgot / reset / verify-email, orders, order-detail, track-order, returns / RMA, subscriptions, wishlist, promotions, brand pages, occasions, contact, about — all code-split and lazy-loaded.
  • CMS-driven content via a DynamicPage + PageBlockRenderer system. Tenants compose pages from typed blocks (hero, product grid, banner, copy block, etc) without touching code.
  • Feature modules are per-tenant. LGT's "Occasions" feature — curated gift catalogues for events like Ramadan and Eid — is a module loaded only for tenants whose config enables it.
  • Per-tenant SEO. Sitemap, robots, Open Graph metadata, and canonical URLs all generated from the tenant config so each brand looks like a first-class native site to search engines.

04Outcome

Two tenants live in production from a single codebase:

  • Garden State — fresh produce and grocery retail, Lebanon.
  • LGT — premium nuts, coffee, dates, and healthy snacks. Adds the "Occasions" feature module on top of the standard catalogue.

Both stores ship the same checkout, the same returns flow, the same account system. Bug fixes and improvements land once and roll out to every tenant on the next deploy.

05What I'd do differently

If I were starting over I'd reach for a server-rendered framework (Next or Remix) sooner. The SPA pays off for the post-login app surface, but the public catalogue and product pages would benefit from SSR for SEO and Time-to-First-Product. The current build is fast enough — but "fast enough" is a worse story than "indexed in 30 seconds."