← Back to Work

Liqaa25

Bilingual Arabic-first professional community platform for Saudi Arabia. Built the full frontend and backend within a 3-person team at Extend.

Role
Senior Frontend Engineer (full-stack)
Published
April 2025
Pages
30+
Route Handlers
70+
Auth strategies
3
Next.js 15React 19TypeScriptTailwind CSS v4Supabasei18nextPostgreSQL
View live site →

Context

Liqaa25 is a professional community platform for KSA — think LinkedIn for a specific professional cohort, built Arabic-first with bilingual RTL/LTR support. The audience is alumni and active members of a professional network: they expect an Arabic-native experience, not a translated interface.

Problem

The core engineering tension: ship a production-grade, Arabic-first RTL platform with dynamic SEO, multi-strategy authentication, and role-based access control — on a tight timeline, with a 3-person team, at the same time as building the backend that powers it.

The technical bets that had to be right from the start:

Architecture

The BFF layer was a deliberate choice over direct Supabase client access. With RLS policies as the security boundary, Route Handlers let us:

Implementation

PostgreSQL schema and RLS

The schema has 10+ tables with Row Level Security policies enforcing RBAC at the database level. A sample policy for the members table:

-- Members can read their own profile and approved members' profiles
create policy "members_select" on members
  for select using (
    auth.uid() = user_id
    or (status = 'approved' and auth.uid() in (
      select user_id from members where status = 'approved'
    ))
  );

Admin-only mutations are gated by a separate policy checking a user_roles join, so even if a Route Handler has a bug, the database won't permit unauthorized writes.

Multi-strategy Auth

All three strategies converge on a single session shape via Supabase Auth. The LinkedIn OAuth flow exchanges the code server-side (Route Handler, not client callback) so the access token never touches the browser. OTP and magic-link flows use Supabase's built-in methods with a shared post-auth hook that seeds the members record on first sign-in.

RTL / i18n

i18next with i18next-resources-to-backend for server-side translation loading. Locale is ar or en, prefixed in the URL. The dir attribute is set on <html> at the layout level — same pattern as this portfolio. One non-obvious issue: Arabic numeral formatting in date strings. Solved by using Intl.DateTimeFormat with locale prop rather than a custom formatter.

Dynamic SEO

JSON-LD Event and Person schemas per page. OG images generated via @vercel/og with IBM Plex Sans Arabic loaded as a font buffer. Auto-generated sitemap via Next.js sitemap.ts metadata route.

Outcome

← All case studies