Skip to content

Architecture

PreviousNext

Four layers kept apart — content, configuration, domain logic and UI.

Four layers

  • Content — events, programs, opportunities, stories, people, companies and partners live in the database and are managed in /dashboard. Each row is written in one language, stored in its lang field.
  • Configuration — the space catalogue, booking products and rules, membership plans, contact channels and navigation live in config files, not the database.
  • Domain logic — the booking service, registration capacity, deadline states and validation schemas. None of it lives in UI components.
  • UI — Server Components by default. Client code is limited to the menu, the language switcher, form state and the booking step enhancer.

Mirror pattern

Routes are thin wrappers. Each page under src/app/[lang]/(site) renders a content.tsx from the matching feature folder in src/components/site/<feature>.

src/app/[lang]/(site)/events/page.tsx
import { EventsContent } from "@/components/site/events/content"
 
export default async function EventsPage(props: PageProps<"/[lang]/events">) {
  const { lang } = await props.params
  return <EventsContent lang={lang} />
}

Request flow

  1. src/proxy.ts redirects bare paths to /ar or /en and makes an optimistic admin cookie check.
  2. The page reads its dictionary and data on the server.
  3. Forms post to Server Actions that validate with Zod, pass the honeypot and rate limit, then write through Prisma.

Security

  • Validation runs on the server for every action.
  • Public forms have a honeypot field and a per-IP rate limit.
  • The admin session is an HMAC-signed, httpOnly, SameSite=Lax cookie, re-verified in every admin page and action.
  • The image optimizer is restricted to allow-listed hosts.