UI for Admin — Directory Architecture
Layer Structure
app/ # Next.js App Router
(auth)/ # Public pages: login, forgot-password, reset-password
(authenticated)/ # Protected layout: fetches /auth/me in a Server Component and enforces role guard
admins/
organizations/
projects/
users/
api/ # Next.js Route Handlers (stateless proxy)
auth/
login/route.ts
logout/route.ts
forgot-password/route.ts
reset-password/route.ts
proxy/[...path]/route.ts # Generic Bearer-injection proxy → Admin API
src/
api/
generated/ # Orval output (types, hooks, Zod) — do not edit manually
validators/ # Hand-authored refinements on top of generated Zod schemas
client.ts # Orval fetcher (points to same-origin /api/proxy)
auth/
session.ts # Cookie read/write helpers (server-only)
guards.ts # can(flag, level) helper
components/ # shadcn + app-specific components
lib/
middleware.ts # Edge-level cookie presence check → redirect to /login
orval.config.ts
Request Flow
Browser ──► Next.js Route Handler (/api/proxy/*) ──► Admin API /api/v1/*
▲
└─ Reads httpOnly Cookie and attaches Authorization: Bearer
The browser never communicates directly with the Admin API. All requests go through the Next.js same-origin proxy.
Horizontal Scaling
The Next.js proxy is completely stateless. Any pod can handle any request; sticky sessions and shared session stores are not required. The only value that needs to be shared across pods is the cookie-signing secret (AWS Secrets Manager / Amplify environment variable).
Actual session state lives in systems that are already horizontally shared:
- Cognito — the JWT itself (self-validated via JWKS)
- PostgreSQL
sessiontable — server-side session rows referenced by the Admin API - Browser — Cookie