Application Layering
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.tsLayer Stack
| 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.
Request & Data Flow
- Routing & Rendering –
src/app/layout.tsxand every route undersrc/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 formactionprops oruseServerActionhooks. - Server Actions (
src/app/actions/*) – Each mutation file is a"use server"module that parses inputs (Zod schemas insrc/app/lib/validations), enforces auth (requireAuthfromsrc/app/lib/auth.ts), delegates to repositories, and returns anActionResult<T>fromsrc/app/lib/action-result.ts. Admin-only actions wrap their handler withwithAdminAuthfromsrc/app/lib/admin-action-wrapper.tsto avoid repeating boilerplate. - Server Queries (
src/app/server/*) – Read-only helpers (e.g.,server/projects/get-project-by-slug.ts) encapsulate Supabase fetches behind caching utilities such ascreateCachedQueryWithParamsinsrc/app/lib/cache.ts. Presentation layers consume these helpers instead of hitting Supabase directly. - Repositories (
src/app/lib/repositories/*) – Concrete data access (e.g.,ProjectRepository,ConversationRepository) inherits frombase-repository.tsand mapsDatabase['public']['Tables']rows into domain objects throughsrc/app/lib/mappers/*. Admin repositories (likeadmin-repository.ts) callcreateAdminClientwhile regular repositories rely oncreateClientorcreateServerClientto respect RLS. - Supabase Clients & Utilities –
src/app/lib/supabase/{server,client,admin}.tscentralize client creation, attach the correct session, and plug into helpers likequery-timeout.tsanderror-handler.ts. Never instantiatecreateBrowserClientorcreateClientoutside this folder. - Caching & Invalidation – All shared cache tags are defined in
src/app/lib/cache.ts. Every mutation must import the relevantrevalidate*helper (e.g.,revalidateProjects) after persisting data so Server Components see fresh content. - Observability & Analytics –
src/app/lib/logger.tsandlogger-edge.tscreate pino-based loggers. Analytics events are centralized insrc/app/lib/analytics-events.tsso server actions log consistent Mixpanel/GA payloads. Feature-specific analytics hooks live insrc/app/hooks/*.
Application Layer Anatomy
- Server Actions Directory Structure –
src/app/actionsmirrors feature domains (ai/,projects/,products/,insights/, etc.). Every folder exposes functions viaindex.tsfor discoverability and has unit tests underactions/__tests__/. - Server Queries – Each file handles one query. Example:
server/projects/get-project-by-slug.tscreates a cached query, fetches the row via Supabase, maps it withmapDbToProject, and logs errors usingcreateServiceLogger('projects'). - ActionResult Pattern –
ActionResult<T>enforces discriminated unions for success/failure. Helper functionssuccess,failure,isSuccess, andgetErrorMessagereduce boilerplate inside actions and repositories. - Admin Wrapper –
withAdminAuthreads the admin session viavalidateAdminAuth, so admin actions only focus on business logic.
Domain & Data Access Layer
| 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. |
Infrastructure Concerns
- Authentication –
src/app/authcontains auth helpers (requireAuth,maybeGetSession, etc.). Middleware insrc/middleware.tswiresnext-intllocales and Supabase cookies so Server Components always know the user. - Rate Limiting –
src/app/lib/rate-limit.tsexposes Upstash-backed throttling, whilesrc/app/lib/api-rate-limit.tsprovides per-route helpers. - External Providers – Stripe lives in
lib/stripe*.ts, Resend inlib/resend.ts, analytics inlib/analytics.ts. Each provider has its own logger (stripe-logger.ts) to keep failure data structured. - Testing & Quality Gates – Vitest config in
vitest.config.ts, Playwright inplaywright.config.ts, plus test helpers undersrc/app/test. Every significant action/query should have coverage inactions/__tests__/orserver/__tests__/.
Workflow Reminders
- Add Zod schema before touching a repository. Schemas live in
lib/validationsand are reused by server actions and components. - Return
ActionResulteverywhere. It makes error handling predictable for UI callers. - Wrap admin mutations. Use
withAdminAuthorwithAdminAuthSimpleto avoid duplicating session checks. - Always revalidate caches. Mutations must import the relevant
revalidate*helper fromlib/cache.ts. - Keep architecture docs updated. If you introduce a new cross-cutting service or data store, add a section here and link to more detailed ADRs.