I adopted a layered architecture for Wedding Ready so each part of the stack owns a clear concern: UI rendering, request boundaries, business operations, and data access/definitions. The goal is to keep ownership boundaries explicit as the product grew, so complexity stays manageable.
The /feed route shows how this works in practice:
Proxy gate: JWT-based authentication is enforced before route rendering, so unauthenticated users are redirected before the server loads /feed.
Server-rendered route shell (RSC): /feed uses React Server Components, handles Suspense and error boundaries around the interactive feed client.
UI components (tiles): tile cards stay presentational ('dumb') with no business logic, so they remain reusable across different contexts.
Client boundary (feed client + useFeed): owns interaction state and client orchestration, not business rules.
Server boundary (API endpoint): handles authentication, request parsing/validation, and invokes operations.
Operations layer: enforces authorization and maps raw model/database outputs into safe response DTOs before returning data to the client.
Data layer: performs typed retrieval using precomputed ranking signals (recency, quality, social) and updates delivery/view state so duplicate tiles are not returned to the feed.
This separation lets me change ranking logic or retrieval strategy without rewriting UI components. On /feed, the API returns ranked tiles and the client pre-hydrates save-state cache entries in the same pass, avoiding N+1 per-tile save-state requests while preserving isolated save/unsave mutations.