This page translates the conventions we follow inside src/app into a set of layers so new contributors can orient themselves before shipping features. It is based on the current code that lives in ../src:
src/
├── app/
│ ├── [locale]/ # Server Components per locale
│ ├── actions/ # Server Actions (mutations)
│ ├── server/ # Server-side queries (reads)
│ ├── lib/ # Shared domain & infra utilities
│ ├── api/ # Route handlers / webhooks
│ ├── components/, hooks/ # Presentation helpers
│ └── types/ # Cross-layer types
├── fonts/, providers/, store/, types/
└── i18n/, middleware.ts| Layer | Purpose | Concrete folders |
|---|---|---|
| Presentation | Render HTML/React output and capture user intent. Server Components go under src/app/[locale]/…; client components live in src/app/components/ and src/app/hooks/. | [locale], components, hooks |
| Application | Orchestrate use-cases. Mutations belong to Server Actions (src/app/actions/*), reads belong to Server Queries (src/app/server/*). Both return structured results via ActionResult. | actions, server, unauthorized |
| Domain | Encapsulate business rules and analytics events. Repository implementations, mappers, services, and validations live under src/app/lib/**. | lib/repositories, lib/mappers, lib/services, lib/validations, lib/analytics-events.ts |
| Infrastructure | Supabase clients, caching, logging, rate limiting, Stripe/Resend integration, and Supabase migrations. | lib/supabase, lib/cache.ts, lib/logger.ts, lib/rate-limit.ts, supabase/migrations/* |
Guiding rule: presentation never talks to Supabase directly. Every data read or mutation flows through the Application layer and eventually through a repository.
src/app/layout.tsx and every route under src/app/[locale]/… render via React Server Components. Client-only features (e.g., src/app/components/chat/chat-input.tsx) mark "use client" and call server actions through form action props or useServerAction hooks.src/app/actions/*) – Each mutation file is a "use server" module that parses inputs (Zod schemas in src/app/lib/validations), enforces auth (requireAuth from src/app/lib/auth.ts), delegates to repositories, and returns an ActionResult<T> from src/app/lib/action-result.ts. Admin-only actions wrap their handler with withAdminAuth from src/app/lib/admin-action-wrapper.ts to avoid repeating boilerplate.src/app/server/*) – Read-only helpers (e.g., server/projects/get-project-by-slug.ts) encapsulate Supabase fetches behind caching utilities such as createCachedQueryWithParams in src/app/lib/cache.ts. Presentation layers consume these helpers instead of hitting Supabase directly.src/app/lib/repositories/*) – Concrete data access (e.g., ProjectRepository, ConversationRepository) inherits from base-repository.ts and maps Database['public']['Tables'] rows into domain objects through src/app/lib/mappers/*. Admin repositories (like admin-repository.ts) call createAdminClient while regular repositories rely on createClient or createServerClient to respect RLS.src/app/lib/supabase/{server,client,admin}.ts centralize client creation, attach the correct session, and plug into helpers like query-timeout.ts and error-handler.ts. Never instantiate createBrowserClient or createClient outside this folder.src/app/lib/cache.ts. Every mutation must import the relevant revalidate* helper (e.g., revalidateProjects) after persisting data so Server Components see fresh content.src/app/lib/logger.ts and logger-edge.ts create pino-based loggers. Analytics events are centralized in src/app/lib/analytics-events.ts so server actions log consistent Mixpanel/GA payloads. Feature-specific analytics hooks live in src/app/hooks/*.src/app/actions mirrors feature domains (ai/, projects/, products/, insights/, etc.). Every folder exposes functions via index.ts for discoverability and has unit tests under actions/__tests__/.server/projects/get-project-by-slug.ts creates a cached query, fetches the row via Supabase, maps it with mapDbToProject, and logs errors using createServiceLogger('projects').ActionResult<T> enforces discriminated unions for success/failure. Helper functions success, failure, isSuccess, and getErrorMessage reduce boilerplate inside actions and repositories.withAdminAuth reads the admin session via validateAdminAuth, so admin actions only focus on business logic.| Component | Key files | Notes |
|---|---|---|
| Repositories | lib/repositories/*.ts | Implement CRUD using Supabase clients. ProjectRepository, MessageRepository, AIRepository, etc. share logging patterns and return typed objects instead of raw rows. |
| Mappers | lib/mappers/* | Translate snake_case database rows (DbProject, DbConversation) to camelCase domain objects consumed by the presentation layer. |
| Services | lib/services/* | Cross-cutting utilities (e.g., payment orchestration, email sending) that coordinate multiple repositories or providers. |
| Validations | lib/validations/* | Zod schemas for forms, server actions (sendMessageSchema in lib/validations/ai.ts, contactValidation in lib/contact-validation.ts). |
| Analytics | lib/analytics-events.ts, lib/analytics.ts, lib/mixpanel*.ts | Provide strongly typed event names so instrumentation stays centralized. |
src/app/auth contains auth helpers (requireAuth, maybeGetSession, etc.). Middleware in src/middleware.ts wires next-intl locales and Supabase cookies so Server Components always know the user.src/app/lib/rate-limit.ts exposes Upstash-backed throttling, while src/app/lib/api-rate-limit.ts provides per-route helpers.lib/stripe*.ts, Resend in lib/resend.ts, analytics in lib/analytics.ts. Each provider has its own logger (stripe-logger.ts) to keep failure data structured.vitest.config.ts, Playwright in playwright.config.ts, plus test helpers under src/app/test. Every significant action/query should have coverage in actions/__tests__/ or server/__tests__/.lib/validations and are reused by server actions and components.ActionResult everywhere. It makes error handling predictable for UI callers.withAdminAuth or withAdminAuthSimple to avoid duplicating session checks.revalidate* helper from lib/cache.ts.