Frontend Architecture
The MemGhost web interface is a modern, responsive Next.js application that provides a rich user experience while maintaining the event-driven architecture of the backend.
Technology Stack
| Library | Purpose |
|---|---|
| Next.js 14+ (App Router) | React framework with server components |
| TypeScript | Type safety throughout |
| Tailwind CSS | Utility-first styling |
| shadcn/ui | Accessible React components (Radix + Tailwind) |
| TanStack Query (React Query) | Server state management |
| Zustand | Client state management |
| react-hook-form + Zod | Form handling with runtime validation |
| EventSource API | Real-time updates via SSE |
Directory Structure
web/src/├── app/ # Next.js App Router│ ├── (auth)/ # Auth layout group (login, callback)│ ├── (dashboard)/ # Protected routes│ │ ├── items/ # VaultItem list, detail, edit, create│ │ ├── people/ # People management│ │ └── tags/ # Tag browsing│ └── layout.tsx # Root layout├── components/ # React components│ ├── ui/ # shadcn/ui components│ └── [module]/ # Module-specific components├── lib/ # Utilities│ ├── api/ # API client & hooks│ ├── auth/ # Authentication (OIDC)│ ├── events/ # Real-time SSE│ └── store/ # Zustand stores└── types/ # TypeScript types (generated from OpenAPI)State Management
Server State (React Query)
React Query manages all API data — caching, automatic refetching, optimistic updates, and cache invalidation.
export function useVaultItems() { return useQuery({ queryKey: ['items'], queryFn: async () => { const response = await apiClient.get('/items'); return response.data; }, });}Client State (Zustand)
Zustand manages UI state: sidebar open/closed, theme preferences, filter state, modal state.
export const useUIStore = create((set) => ({ sidebarOpen: true, setSidebarOpen: (open) => set({ sidebarOpen: open }),}));Real-Time Updates
The UI connects to the SSE event stream on login. When events arrive, React Query caches are invalidated and relevant queries are refetched automatically:
export function useEvents() { const queryClient = useQueryClient();
useEffect(() => { eventSourceManager.connect(); eventSourceManager.subscribe('item.created.v1', () => { queryClient.invalidateQueries({ queryKey: ['items'] }); }); }, [queryClient]);}Authentication
Using OpenID Connect with the backend as identity provider:
- User navigates to a protected route
- Redirected to login if not authenticated
- OIDC authentication flow completes
- Token stored in localStorage and included in API requests
- Automatic token refresh on expiration
Routes under (dashboard)/ are protected and redirect to login when unauthenticated.
Component Patterns
- Server Components — initial page load, SEO-friendly, fast render
- Client Components — forms, real-time updates, user interactions
- Form Handling — react-hook-form with Zod validation schemas
Performance
- Server Components for initial data fetch
- React Query caching avoids redundant API calls
- Optimistic updates for immediate UI feedback
- Code splitting with dynamic imports
- Next.js Image component for optimization
Accessibility
- WCAG AA compliance target
- Keyboard navigation throughout
- Screen reader support with ARIA labels
- Focus management on navigation
- All shadcn/ui components are accessible by default