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:
- Routing and i18n architecture — i18next with Next.js App Router, locale-prefixed routes, server-side translation loading. Wrong choice here and you're refactoring at 70% completion.
- Auth strategy — three separate authentication flows (LinkedIn OAuth for professionals, OTP for mobile, magic links for email-first users). Multi-strategy Supabase Auth with unified session handling.
- BFF vs. direct client access — Supabase JS SDK from the client is fast to ship but leaks service keys and bypasses your own rate limiting. Route Handlers as BFF keeps auth logic server-side.
Architecture
The BFF layer was a deliberate choice over direct Supabase client access. With RLS policies as the security boundary, Route Handlers let us:
- Keep the service-role key server-only (never shipped to the browser)
- Aggregate multiple Supabase queries into a single HTTP round-trip
- Apply domain-specific validation and rate limiting before hitting the database
- Version the API surface independently from the frontend
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
- 30+ pages live across three role-based personas (admin, member, alumni), including events, directory, members, articles, reports, intro requests, moderation, applications/companies workflow, sponsors, newsletter, and a content library
- 70+ Route Handlers covering authentication (LinkedIn / OTP / magic link), profile and directory management, events and registrations, articles, reports, sponsors, intro requests, moderation, admin user/role/company management, and admin appconfig
- Arabic-first RTL reviewed by native speakers and deployed to production
- Dynamic SEO indexed; OG images render correctly in Arabic for LinkedIn shares
- Internal API surface designed to be reusable across other products in the same product family