Architecture
1. Summary
The Portal Frontend is built using a clear, modular architecture that keeps routing, UI composition, state management, network communication, and data validation concerns separate.
2. System Architecture Overview
2.1 Layered Architecture (High-Level)
┌─────────────────────────────────────────────────────┐
│ 1. ROUTING LAYER - Page Entry Points │
│ Server Components, App Router pages │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 2. COMPONENT LAYER - UI Composition │
│ Layout shells, domain forms/tables, Shadcn primitives │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 3. STATE & DATA LAYER - Logic Coordination │
│ React Query hooks, URL state hooks, form hooks │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 4. API SERVICE LAYER - Network Communication │
│ Client requests, server requests, API proxy route │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 5. TYPE & VALIDATION LAYER - Contracts │
│ Zod schemas, TypeScript interfaces, form schemas │
└─────────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────────┐
│ 6. UTILITY LAYER - Shared Helpers │
│ Mappers, formatters, server fetch, logger │
└─────────────────────────────────────────────────────────────────┘
2.2 Technology Stack Summary
- Framework: Next.js 15 (App Router)
- UI Library: React 19
- Language: TypeScript (strict)
- Server State: TanStack React Query
- Forms: React Hook Form + Zod
- UI Primitives: Radix UI / Shadcn
- Styling: Tailwind CSS
- Authentication: NextAuth (Keycloak)
- Internationalization: next-intl
- HTTP Client: Axios (client-side), native fetch (server-side)
- Testing: Vitest, Testing Library, Playwright, Storybook
3. Architectural Components
This section describes the key components that make up each layer of the frontend architecture. While Section 2 focuses on the structure of the architecture, this section focuses on the purpose and role of each component.
3.1 Pages (Routing Layer)
Component Name: Page Primary Role: Entry point for all user navigation. Responsibilities:
- Receive URL and search parameters from the router
- Perform server-side data fetching
- Compose layout components and delegate rendering to child components
- Define route-level error boundaries
Pages keep the routing surface stable even as internal components and data flows evolve.
3.2 Components (Component Layer)
Component Name: Component Primary Role: Compose and present the user interface. Responsibilities:
- Render UI from base primitives (Shadcn/Radix)
- Provide consistent page structure via layout components
- Present domain data through forms, tables, and detail views
- Keep business and data-fetching logic out of rendering
Components are organized in three tiers:
- UI Primitives – Base Shadcn/Radix components (Button, Dialog, Table, Select, etc.)
- Layout Components – Page structure shells (PageContainer, PageHeader, PageBackground, PageEditControls)
- Domain Components – Feature-specific forms, tables, list views, and detail views, colocated within each feature's route folder
3.3 Hooks (State & Data Layer)
Component Name: Hook Primary Role: Core coordinator of client-side logic and state. Responsibilities:
- Manage data fetching and cache via React Query
- Handle URL-driven state for sorting, pagination, and search
- Coordinate form state and validation
- Provide a stable interface between components and the API layer
Generic hooks abstract all React Query boilerplate:
- useDataQuery – GET requests with automatic query key management
- useCreateMutation – POST operations with cache invalidation
- useUpdateMutation – PUT/PATCH operations with cache invalidation
- useDeleteMutation – DELETE operations with cache invalidation
- useTableSearchParams – URL-based table state (sort, page, search)
Hooks represent the authoritative coordination logic of the client.
3.4 API Services (API Service Layer)
Component Name: Service Primary Role: Structured access to the backend API. Responsibilities:
- Abstract all network communication behind a consistent interface
- Manage authentication tokens (JWT extraction and forwarding)
- Normalize response shapes across different backend endpoints
- Isolate backend API details from the rest of the application
Each entity domain provides two service files:
- Client Requests – Thin wrappers composing generic hooks with entity-specific keys, types, and error messages (used in Client Components)
- Server Requests – Async functions using direct fetch with JWT tokens (used in Server Components)
All client-side requests are routed through a catch-all API proxy route that attaches Bearer tokens and forwards requests to the backend.
3.5 Schemas & Types (Type & Validation Layer)
Component Name: Schema / Type Primary Role: Define the application's core data structures and validation rules. Responsibilities:
- Serve as single source of truth for data shapes (Zod schemas with TypeScript inference)
- Validate data at runtime boundaries
- Enforce progressive validation (Draft vs. Available)
- Transform API response structures into form-compatible shapes
- Maintain separation between API-facing and UI-facing structures
Each entity defines multiple schema variants:
- API Response Schema – Matches the backend OutputDTO
- Draft Form Schema – Permissive validation for incomplete entities
- Available Form Schema – Strict validation required before publishing
- Transform Schema – Reshapes API data into form-compatible structures
3.6 Utilities (Utility Layer)
Component Name: Utility Primary Role: Transform data between different structures. Responsibilities:
- Convert between API response shapes and UI-compatible structures
- Extract only changed fields for partial updates (PATCH)
- Format dates, build request parameters, map enums
- Keep transformation logic consistent and stateless
Utilities ensure data moves cleanly and safely across architectural boundaries.
4. Component Interactions
4.1 Example Flow – Loading a List Page
Browser Request → Routing Layer → Server-Side Data Fetch → API Service Layer → Backend API
↓
Component Layer → Browser
The page (Server Component) receives the request, calls the server request function which fetches data directly from the backend with a JWT token, then renders layout and list components with the fetched data.
4.2 Example Flow – Creating an Entity
User Input → Component Layer → Hook Layer → API Service Layer → API Proxy → Backend API
↓
Cache Invalidation → Component Re-render
The form component collects input, Zod validates the data, the create mutation hook sends a POST through the API proxy, and on success the query cache is invalidated causing the list to refresh.
4.3 Example Flow – Updating with Partial Data (PATCH)
User Edits Fields → Form Tracks Dirty Fields → Utility Extracts Changes → Hook Layer → API Service Layer → Backend API
React Hook Form tracks which fields have been modified. On submit, only changed fields are extracted and sent as a PATCH request, minimizing payload size and avoiding unintended overwrites.
4.4 Dependency Direction (Strict)
- Pages → Components → Hooks → API Services → Schemas/Types
- No layer reaches back "upward"
- No cross-layer shortcuts
This ensures predictability and reduces long-term coupling.
5. Key Architectural Choices
5.1 Server/Client Component Split
List pages are Server Components for initial data fetching and performance. Detail views and interactive elements are Client Components. This keeps the initial payload lean and leverages server-side rendering where beneficial.
5.2 API Proxy Pattern
All client-side requests route through a Next.js catch-all API route rather than directly to the backend. This keeps backend URLs and authentication tokens server-side only, simplifies CORS handling, and allows seamless switching between the real API and a mock server during development.
5.3 Dual Data Fetching Paths
Server Components use direct fetch with JWT tokens extracted from the session cookie. Client Components use React Query hooks through the API proxy. Both paths normalize responses to the same shape, ensuring consistent data handling regardless of rendering context.
5.4 Generic Mutation and Query Hooks
Four generic hooks abstract all React Query configuration. Entity-specific service files are thin wrappers supplying only the endpoint key, types, and error messages. Adding a new entity requires zero custom data-fetching code.
5.5 URL-Driven Table State
Sorting, pagination, and search filters are stored as URL search parameters. This makes table state shareable via links, bookmarkable, and compatible with server-side rendering and browser navigation.
5.6 Native-First State Management
Client-side state relies on React's built-in State and Context APIs rather than external state management libraries. Server state is handled exclusively by React Query. This keeps the dependency footprint small, avoids redundant caching layers, and follows the principle of preferring native platform capabilities over third-party abstractions.
5.7 Feature-Driven File Structure
The codebase combines feature-driven architecture with component-driven architecture. Full features (pages, hooks, services, schemas) are colocated within their route folders, while shared UI primitives live in a central component directory powered by Shadcn. This keeps related code close together and reduces cross-feature coupling.
5.8 Form Handling with React Hook Form and Zod
Forms use React Hook Form for state management and Zod for schema-based validation. React Hook Form minimizes re-renders through uncontrolled inputs and integrates natively with Shadcn form components. Zod schemas serve double duty as both runtime validators and TypeScript type sources, eliminating drift between validation rules and type definitions.
5.9 Axios as API Client
Client-side HTTP communication uses Axios rather than the native fetch API. Axios provides automatic JSON transformation, request and response interceptors for cross-cutting concerns like error logging, and a consistent interface across the application. Server Components use native fetch directly, as Axios's browser-oriented features are not needed in that context.
5.10 Authentication via NextAuth with Keycloak
Authentication is handled by NextAuth (Auth.js) connected to Keycloak as the OpenID Connect provider. This centralizes session and token management on the server, keeps JWTs out of the browser, and delegates identity concerns to a dedicated infrastructure component. The choice of Auth.js ensures tight integration with Next.js middleware and route protection.
5.11 Internationalization with next-intl
All user-facing text is managed through next-intl, which provides translation functions for both Server and Client Components. Message files are organized by locale, and validation error messages reference translation keys. This ensures consistent multilingual support without coupling translation logic to individual components.
5.12 Testing Strategy
The testing approach uses three complementary layers: Vitest with Testing Library for unit and component tests, Playwright for end-to-end tests, and Storybook for visual component documentation and isolation testing. ESLint enforces static code quality. This layered strategy ensures fast feedback during development while maintaining confidence in integration and user-facing behavior.
6. Cross-Cutting Concerns
6.1 Authentication
NextAuth with Keycloak handles OpenID Connect authentication. Middleware redirects unauthenticated users. JWT tokens are managed server-side and never exposed to the browser. Automatic token refresh with expiration detection keeps sessions alive.
6.2 Security
Content Security Policy middleware generates per-request nonces for script execution. Production mode enforces strict nonce-only policies. Additional headers prevent MIME sniffing, clickjacking, and referrer leakage.
6.3 Performance
- React Query caching with configurable stale time prevents redundant fetches
- Placeholder data avoids layout shift during refetches
- Server-side data fetching for list pages eliminates client-side loading waterfalls
- Pagination everywhere
6.4 Validation
- Schema-level validation via Zod at form submission
- Field-level validation via React Hook Form on change
- Backend constraints as final enforcement
- Dirty field extraction ensures PATCH requests send only changed values
6.5 Internationalization
Server and client translation functions via next-intl. Message files organized by locale. Validation error messages reference translation keys for consistent multilingual feedback.
6.6 Error Handling
- Route-level error boundaries via Next.js error pages
- API-level error logging with context
- Toast notifications for user-facing feedback
- Graceful fallback UI for unexpected failures
7. Glossary
- Page – Server Component entry point for a route
- Component – Reusable UI building block, either a base primitive or a domain-specific composition
- Hook – Custom React function encapsulating state management, data fetching, or side effects
- Client Requests – React Query hook wrappers for browser-side API calls through the proxy
- Server Requests – Async functions for direct backend communication in Server Components
- Schema – Zod object defining both runtime validation rules and TypeScript types
- API Proxy – Next.js catch-all API route forwarding requests to the backend with authentication
- Draft Schema – Permissive validation for entities in Draft status
- Available Schema – Strict validation required before an entity can be published