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 itslangfield. - 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>.
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
src/proxy.tsredirects bare paths to/aror/enand makes an optimistic admin cookie check.- The page reads its dictionary and data on the server.
- 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.