forked from cardosofelipe/fast-next-template
- Deleted `I18N_IMPLEMENTATION_PLAN.md` and `PROJECT_PROGRESS.md` to declutter the repository. - These documents were finalized, no longer relevant, and superseded by implemented features and external references.
1564 lines
43 KiB
Markdown
Executable File
1564 lines
43 KiB
Markdown
Executable File
# Frontend Architecture Documentation
|
|
|
|
**Project**: Next.js + FastAPI Template
|
|
**Version**: 1.0
|
|
**Last Updated**: 2025-10-31
|
|
**Status**: Living Document
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [System Overview](#1-system-overview)
|
|
2. [Technology Stack](#2-technology-stack)
|
|
3. [Architecture Patterns](#3-architecture-patterns)
|
|
4. [Data Flow](#4-data-flow)
|
|
5. [State Management Strategy](#5-state-management-strategy)
|
|
6. [Authentication Architecture](#6-authentication-architecture)
|
|
7. [API Integration](#7-api-integration)
|
|
8. [Routing Strategy](#8-routing-strategy)
|
|
9. [Component Organization](#9-component-organization)
|
|
10. [Testing Strategy](#10-testing-strategy)
|
|
11. [Performance Considerations](#11-performance-considerations)
|
|
12. [Security Architecture](#12-security-architecture)
|
|
13. [Design Decisions & Rationale](#13-design-decisions--rationale)
|
|
14. [Deployment Architecture](#14-deployment-architecture)
|
|
|
|
---
|
|
|
|
## 1. System Overview
|
|
|
|
### 1.1 Purpose
|
|
|
|
This frontend template provides a production-ready foundation for building modern web applications with Next.js 16 and FastAPI backend integration. It implements comprehensive authentication, admin dashboards, user management, and organization management out of the box.
|
|
|
|
### 1.2 High-Level Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Next.js Frontend │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ App Router (RSC) │ Client Components │ API Routes │
|
|
├────────────────────┼────────────────────┼──────────────────┤
|
|
│ Pages & Layouts │ Interactive UI │ Middleware │
|
|
│ (Server-side) │ (Client-side) │ (Auth Guards) │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
↓ ↑
|
|
┌──────────────────────┐
|
|
│ State Management │
|
|
├──────────────────────┤
|
|
│ TanStack Query │ ← Server State
|
|
│ (React Query v5) │
|
|
├──────────────────────┤
|
|
│ Zustand Stores │ ← Client State
|
|
│ (Auth, UI) │
|
|
└──────────────────────┘
|
|
↓ ↑
|
|
┌──────────────────────┐
|
|
│ API Client Layer │
|
|
├──────────────────────┤
|
|
│ Axios Instance │
|
|
│ + Interceptors │
|
|
├──────────────────────┤
|
|
│ Generated Client │
|
|
│ (OpenAPI → TS) │
|
|
└──────────────────────┘
|
|
↓ ↑
|
|
┌──────────────────────┐
|
|
│ FastAPI Backend │
|
|
│ /api/v1/* │
|
|
└──────────────────────┘
|
|
```
|
|
|
|
### 1.3 Key Features
|
|
|
|
- **Authentication**: JWT-based with token rotation, per-device session tracking
|
|
- **Admin Dashboard**: User management, organization management, analytics
|
|
- **State Management**: TanStack Query for server state, Zustand for auth/UI
|
|
- **Type Safety**: Full TypeScript with generated types from OpenAPI spec
|
|
- **Component Library**: shadcn/ui with Radix UI primitives
|
|
- **Testing**: 90%+ coverage target with Jest, React Testing Library, Playwright
|
|
- **Accessibility**: WCAG 2.1 Level AA compliance
|
|
- **Dark Mode**: Full theme support with Tailwind CSS
|
|
|
|
---
|
|
|
|
## 2. Technology Stack
|
|
|
|
### 2.1 Core Framework
|
|
|
|
**Next.js 16.x (App Router)**
|
|
|
|
- **Why**: Modern React framework with RSC, excellent DX, optimized performance
|
|
- **App Router**: Preferred over Pages Router for better data fetching, layouts, and streaming
|
|
- **Server Components**: Default for better performance, client components for interactivity
|
|
- **TypeScript**: Strict mode enabled for maximum type safety
|
|
|
|
### 2.2 State Management
|
|
|
|
**TanStack Query (React Query v5)**
|
|
|
|
- **Purpose**: Server state management (all API data)
|
|
- **Why**: Automatic caching, background refetching, request deduplication, optimistic updates
|
|
- **Usage**: All data fetching goes through React Query hooks
|
|
|
|
**Zustand 4.x**
|
|
|
|
- **Purpose**: Client-only state (authentication, UI preferences)
|
|
- **Why**: Minimal boilerplate, no Context API overhead, simple API
|
|
- **Usage**: Auth store, UI store (sidebar, theme, modals)
|
|
- **Philosophy**: Use sparingly, prefer server state via React Query
|
|
|
|
### 2.3 UI Layer
|
|
|
|
**shadcn/ui**
|
|
|
|
- **Why**: Accessible components (Radix UI), customizable, copy-paste (not npm dependency)
|
|
- **Components**: Button, Card, Dialog, Form, Input, Table, Toast, etc.
|
|
- **Customization**: Tailwind-based, easy to adapt to design system
|
|
|
|
**Tailwind CSS 4.x**
|
|
|
|
- **Why**: Utility-first, excellent DX, small bundle size, dark mode support
|
|
- **Strategy**: Class-based dark mode, mobile-first responsive design
|
|
- **Customization**: Custom theme colors, design tokens
|
|
|
|
**Recharts 2.x**
|
|
|
|
- **Purpose**: Charts for admin dashboard
|
|
- **Why**: React-native, composable, responsive, themed with Tailwind colors
|
|
|
|
### 2.4 API Layer
|
|
|
|
**@hey-api/openapi-ts**
|
|
|
|
- **Purpose**: Generate TypeScript client from backend OpenAPI spec
|
|
- **Why**: Type-safe API calls, auto-generated types matching backend
|
|
- **Alternative**: Considered `openapi-typescript-codegen` but this is more actively maintained
|
|
|
|
**Axios 1.x**
|
|
|
|
- **Purpose**: HTTP client for API calls
|
|
- **Why**: Interceptor support for auth, better error handling than fetch
|
|
- **Usage**: Wrapped in generated API client, configured with auth interceptors
|
|
|
|
### 2.5 Forms & Validation
|
|
|
|
**react-hook-form 7.x**
|
|
|
|
- **Purpose**: Form state management
|
|
- **Why**: Excellent performance, minimal re-renders, great DX
|
|
|
|
**Zod 3.x**
|
|
|
|
- **Purpose**: Runtime type validation and schema definition
|
|
- **Why**: Type inference, composable schemas, integrates with react-hook-form
|
|
- **Usage**: All forms use Zod schemas with `zodResolver`
|
|
|
|
### 2.6 Testing
|
|
|
|
**Jest + React Testing Library**
|
|
|
|
- **Purpose**: Unit and component tests
|
|
- **Why**: Industry standard, excellent React support, accessibility-focused
|
|
|
|
**Playwright**
|
|
|
|
- **Purpose**: End-to-end testing
|
|
- **Why**: Fast, reliable, multi-browser, great debugging tools
|
|
- **Coverage Target**: 90%+ for template robustness
|
|
|
|
### 2.7 Additional Libraries
|
|
|
|
- **date-fns**: Date manipulation and formatting (lighter than moment.js)
|
|
- **clsx** + **tailwind-merge**: Conditional class names with conflict resolution
|
|
- **lucide-react**: Icon system (tree-shakeable, consistent design)
|
|
|
|
---
|
|
|
|
## 3. Architecture Patterns
|
|
|
|
### 3.1 Layered Architecture
|
|
|
|
Inspired by backend's 5-layer architecture, frontend follows similar separation of concerns:
|
|
|
|
```
|
|
┌────────────────────────────────────────────────────────────┐
|
|
│ Layer 1: Pages & Layouts (app/*) │
|
|
│ - Route definitions, page components, layouts │
|
|
│ - Mostly Server Components, minimal logic │
|
|
│ - Delegates to hooks and components │
|
|
└────────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌────────────────────────────────────────────────────────────┐
|
|
│ Layer 2: React Hooks (hooks/, lib/api/hooks/) │
|
|
│ - Custom hooks for component logic │
|
|
│ - React Query hooks for data fetching │
|
|
│ - Reusable logic extraction │
|
|
└────────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌────────────────────────────────────────────────────────────┐
|
|
│ Layer 3: Services (services/) │
|
|
│ - Business logic (if complex) │
|
|
│ - Multi-step operations │
|
|
│ - Data transformations │
|
|
└────────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌────────────────────────────────────────────────────────────┐
|
|
│ Layer 4: API Client (lib/api/*) │
|
|
│ - Axios instance with interceptors │
|
|
│ - Generated API client from OpenAPI │
|
|
│ - Error handling │
|
|
└────────────────────────────────────────────────────────────┘
|
|
↓
|
|
┌────────────────────────────────────────────────────────────┐
|
|
│ Layer 5: Types & Models (types/, lib/api/generated/) │
|
|
│ - TypeScript interfaces │
|
|
│ - Generated types from OpenAPI │
|
|
│ - Validation schemas (Zod) │
|
|
└────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
**Key Rules:**
|
|
|
|
- Pages/Layouts should NOT contain business logic
|
|
- Components should NOT call API client directly (use hooks)
|
|
- Hooks should NOT contain display logic
|
|
- API client should NOT contain business logic
|
|
- Types should NOT import from upper layers
|
|
|
|
### 3.2 Component Patterns
|
|
|
|
**Server Components by Default:**
|
|
|
|
```typescript
|
|
// app/(authenticated)/admin/users/page.tsx
|
|
// Server Component - can fetch data directly
|
|
export default async function UsersPage() {
|
|
// Could fetch data here, but we delegate to client components with React Query
|
|
return (
|
|
<div>
|
|
<PageHeader title="Users" />
|
|
<UserTable /> {/* Client Component with data fetching */}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
**Client Components for Interactivity:**
|
|
|
|
```typescript
|
|
// components/admin/UserTable.tsx
|
|
'use client';
|
|
|
|
import { useUsers } from '@/lib/api/hooks/useUsers';
|
|
|
|
export function UserTable() {
|
|
const { data, isLoading, error } = useUsers();
|
|
// ... render logic
|
|
}
|
|
```
|
|
|
|
**Composition Over Prop Drilling:**
|
|
|
|
```typescript
|
|
// Good: Use composition
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Users</CardTitle>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<UserTable />
|
|
</CardContent>
|
|
</Card>
|
|
|
|
// Avoid: Deep prop drilling
|
|
<Card title="Users" content={<UserTable />} />
|
|
```
|
|
|
|
### 3.3 Single Responsibility Principle
|
|
|
|
Each module has one clear responsibility:
|
|
|
|
- **Pages**: Routing and layout structure
|
|
- **Components**: UI rendering and user interaction
|
|
- **Hooks**: Data fetching and reusable logic
|
|
- **Services**: Complex business logic (multi-step operations)
|
|
- **API Client**: HTTP communication
|
|
- **Stores**: Global client state
|
|
- **Types**: Type definitions
|
|
|
|
---
|
|
|
|
## 4. Data Flow
|
|
|
|
### 4.1 Request Flow (API Call)
|
|
|
|
```
|
|
┌──────────────┐
|
|
│ User Action │ (e.g., Click "Save User")
|
|
└──────┬───────┘
|
|
↓
|
|
┌──────────────────┐
|
|
│ Component │ Calls hook: updateUser.mutate(data)
|
|
└──────┬───────────┘
|
|
↓
|
|
┌──────────────────┐
|
|
│ React Query Hook │ useMutation with API client call
|
|
│ (useUpdateUser) │
|
|
└──────┬───────────┘
|
|
↓
|
|
┌──────────────────┐
|
|
│ API Client │ Axios PUT request with interceptors
|
|
│ (Axios) │
|
|
└──────┬───────────┘
|
|
↓
|
|
┌──────────────────┐
|
|
│ Request │ Add Authorization header
|
|
│ Interceptor │ token = authStore.accessToken
|
|
└──────┬───────────┘
|
|
↓
|
|
┌──────────────────┐
|
|
│ FastAPI Backend │ PUT /api/v1/users/{id}
|
|
└──────┬───────────┘
|
|
↓
|
|
┌──────────────────┐
|
|
│ Response │ Check status code
|
|
│ Interceptor │ - 401: Refresh token → retry
|
|
│ │ - 200: Parse success
|
|
│ │ - 4xx/5xx: Parse error
|
|
└──────┬───────────┘
|
|
↓
|
|
┌──────────────────┐
|
|
│ React Query │ Cache invalidation
|
|
│ │ queryClient.invalidateQueries(['users'])
|
|
└──────┬───────────┘
|
|
↓
|
|
┌──────────────────┐
|
|
│ Component │ Re-renders with updated data
|
|
│ (via useUsers) │ Shows success toast
|
|
└──────────────────┘
|
|
```
|
|
|
|
### 4.2 Authentication Flow
|
|
|
|
```
|
|
┌──────────────┐
|
|
│ Login Form │ User enters email + password
|
|
└──────┬───────┘
|
|
↓
|
|
┌──────────────────────┐
|
|
│ authStore.login() │ Zustand action
|
|
└──────┬───────────────┘
|
|
↓
|
|
┌──────────────────────┐
|
|
│ API: POST /auth/login│ Backend validates credentials
|
|
└──────┬───────────────┘
|
|
↓
|
|
┌──────────────────────┐
|
|
│ Backend Response │ { access_token, refresh_token, user }
|
|
└──────┬───────────────┘
|
|
↓
|
|
┌──────────────────────┐
|
|
│ authStore.setTokens()│ Store tokens (sessionStorage + localStorage/cookie)
|
|
│ authStore.setUser() │ Store user object
|
|
└──────┬───────────────┘
|
|
↓
|
|
┌──────────────────────┐
|
|
│ Axios Interceptor │ Now adds Authorization header to all requests
|
|
└──────┬───────────────┘
|
|
↓
|
|
┌──────────────────────┐
|
|
│ Redirect to Home │ User is authenticated
|
|
└──────────────────────┘
|
|
```
|
|
|
|
**Token Refresh Flow (Automatic):**
|
|
|
|
```
|
|
API Request → 401 Response → Check if refresh token exists
|
|
↓ Yes ↓ No
|
|
POST /auth/refresh Redirect to Login
|
|
↓
|
|
New Tokens → Update Store → Retry Original Request
|
|
```
|
|
|
|
### 4.3 State Updates
|
|
|
|
**Server State (React Query):**
|
|
|
|
- Automatic background refetch
|
|
- Cache invalidation on mutations
|
|
- Optimistic updates where appropriate
|
|
|
|
**Client State (Zustand):**
|
|
|
|
- Direct store updates
|
|
- No actions/reducers boilerplate
|
|
- Subscriptions for components
|
|
|
|
---
|
|
|
|
## 5. State Management Strategy
|
|
|
|
### 5.1 Philosophy
|
|
|
|
**Use the Right Tool for the Right Job:**
|
|
|
|
- Server data → TanStack Query
|
|
- Auth & tokens → Zustand
|
|
- UI state → Zustand (minimal)
|
|
- Form state → react-hook-form
|
|
- Component state → useState/useReducer
|
|
|
|
**Avoid Redundancy:**
|
|
|
|
- DON'T duplicate server data in Zustand
|
|
- DON'T store API responses in global state
|
|
- DO keep state as local as possible
|
|
|
|
### 5.2 TanStack Query Configuration
|
|
|
|
**Global Config** (`src/config/queryClient.ts`):
|
|
|
|
```typescript
|
|
export const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
staleTime: 60000, // 1 minute
|
|
cacheTime: 300000, // 5 minutes
|
|
retry: 3, // Retry failed requests
|
|
refetchOnWindowFocus: true, // Refetch on tab focus
|
|
refetchOnReconnect: true, // Refetch on network reconnect
|
|
},
|
|
mutations: {
|
|
retry: 1, // Retry mutations once
|
|
},
|
|
},
|
|
});
|
|
```
|
|
|
|
**Query Key Structure:**
|
|
|
|
```typescript
|
|
['users'][('users', userId)][('users', { page: 1, search: 'john' })][ // List all users // Single user // Filtered list
|
|
('organizations', orgId, 'members')
|
|
]; // Nested resource
|
|
```
|
|
|
|
### 5.3 Zustand Stores
|
|
|
|
**Auth Store** (`src/stores/authStore.ts`):
|
|
|
|
```typescript
|
|
interface AuthStore {
|
|
user: User | null;
|
|
accessToken: string | null;
|
|
refreshToken: string | null;
|
|
isAuthenticated: boolean;
|
|
isLoading: boolean;
|
|
login: (credentials) => Promise<void>;
|
|
logout: () => Promise<void>;
|
|
logoutAll: () => Promise<void>;
|
|
setTokens: (access, refresh) => void;
|
|
clearAuth: () => void;
|
|
}
|
|
```
|
|
|
|
**UI Store** (`src/stores/uiStore.ts`):
|
|
|
|
```typescript
|
|
interface UIStore {
|
|
sidebarOpen: boolean;
|
|
theme: 'light' | 'dark' | 'system';
|
|
setSidebarOpen: (open: boolean) => void;
|
|
toggleSidebar: () => void;
|
|
setTheme: (theme) => void;
|
|
}
|
|
```
|
|
|
|
**Store Guidelines:**
|
|
|
|
- Keep stores small and focused
|
|
- Use selectors for computed values
|
|
- Persist to localStorage where appropriate
|
|
- Document why Zustand over alternatives
|
|
|
|
---
|
|
|
|
## 6. Authentication Architecture
|
|
|
|
### 6.1 Context-Based Dependency Injection Pattern
|
|
|
|
**Architecture Overview:**
|
|
|
|
This project uses a **hybrid authentication pattern** combining Zustand for state management and React Context for dependency injection. This provides the best of both worlds:
|
|
|
|
```
|
|
Component → useAuth() hook → AuthContext → Zustand Store → Storage Layer → Crypto (AES-GCM)
|
|
↓
|
|
Injectable for tests
|
|
↓
|
|
Production: Real store | Tests: Mock store
|
|
```
|
|
|
|
**Why This Pattern?**
|
|
|
|
✅ **Benefits:**
|
|
|
|
- **Testable**: E2E tests can inject mock stores without backend
|
|
- **Performant**: Zustand handles state efficiently, Context is just a thin wrapper
|
|
- **Type-safe**: Full TypeScript inference throughout
|
|
- **Maintainable**: Clear separation (Context = DI, Zustand = state)
|
|
- **Extensible**: Easy to add auth events, middleware, logging
|
|
- **React-idiomatic**: Follows React best practices
|
|
|
|
**Key Design Principles:**
|
|
|
|
1. **Thin Context Layer**: Context only provides dependency injection, no business logic
|
|
2. **Zustand for State**: All state management stays in Zustand (no duplicated state)
|
|
3. **Backward Compatible**: Internal refactor only, no API changes
|
|
4. **Type Safe**: Context interface exactly matches Zustand store interface
|
|
5. **Performance**: Context value is stable (no unnecessary re-renders)
|
|
|
|
### 6.2 Implementation Components
|
|
|
|
#### AuthContext Provider (`src/lib/auth/AuthContext.tsx`)
|
|
|
|
**Purpose**: Wraps Zustand store in React Context for dependency injection
|
|
|
|
```typescript
|
|
// Accepts optional store prop for testing
|
|
<AuthProvider store={mockStore}> // Unit tests
|
|
<App />
|
|
</AuthProvider>
|
|
|
|
// Or checks window global for E2E tests
|
|
window.__TEST_AUTH_STORE__ = mockStoreHook;
|
|
|
|
// Or uses production singleton (default)
|
|
<AuthProvider>
|
|
<App />
|
|
</AuthProvider>
|
|
```
|
|
|
|
**Implementation Details:**
|
|
|
|
- Stores Zustand hook function (not state) in Context
|
|
- Priority: explicit prop → E2E test store → production singleton
|
|
- Type-safe window global extension for E2E injection
|
|
- Calls hook internally (follows React Rules of Hooks)
|
|
|
|
#### useAuth Hook (Polymorphic)
|
|
|
|
**Supports two usage patterns:**
|
|
|
|
```typescript
|
|
// Pattern 1: Full state access (simple)
|
|
const { user, isAuthenticated } = useAuth();
|
|
|
|
// Pattern 2: Selector (optimized for performance)
|
|
const user = useAuth((state) => state.user);
|
|
```
|
|
|
|
**Why Polymorphic?**
|
|
|
|
- Simple pattern for most use cases
|
|
- Optimized pattern available when needed
|
|
- Type-safe with function overloads
|
|
- No performance overhead
|
|
|
|
**Critical Implementation Detail:**
|
|
|
|
```typescript
|
|
export function useAuth(): AuthState;
|
|
export function useAuth<T>(selector: (state: AuthState) => T): T;
|
|
export function useAuth<T>(selector?: (state: AuthState) => T): AuthState | T {
|
|
const storeHook = useContext(AuthContext);
|
|
if (!storeHook) {
|
|
throw new Error('useAuth must be used within AuthProvider');
|
|
}
|
|
// CRITICAL: Call the hook internally (follows React Rules of Hooks)
|
|
return selector ? storeHook(selector) : storeHook();
|
|
}
|
|
```
|
|
|
|
**Do NOT** return the hook function itself - this violates React Rules of Hooks!
|
|
|
|
### 6.3 Usage Patterns
|
|
|
|
#### For Components (Rendering Auth State)
|
|
|
|
**Use `useAuth()` from Context:**
|
|
|
|
```typescript
|
|
import { useAuth } from '@/lib/stores';
|
|
|
|
function MyComponent() {
|
|
// Full state access
|
|
const { user, isAuthenticated } = useAuth();
|
|
|
|
// Or with selector for optimization
|
|
const user = useAuth(state => state.user);
|
|
|
|
if (!isAuthenticated) {
|
|
return <LoginPrompt />;
|
|
}
|
|
|
|
return <div>Hello, {user?.first_name}!</div>;
|
|
}
|
|
```
|
|
|
|
**Why?**
|
|
|
|
- Component re-renders when auth state changes
|
|
- Type-safe access to all state properties
|
|
- Clean, idiomatic React code
|
|
|
|
#### For Mutation Callbacks (Updating Auth State)
|
|
|
|
**Use `useAuthStore.getState()` directly:**
|
|
|
|
```typescript
|
|
import { useAuthStore } from '@/lib/stores/authStore';
|
|
|
|
export function useLogin() {
|
|
return useMutation({
|
|
mutationFn: async (data) => {
|
|
const response = await loginAPI(data);
|
|
|
|
// Access store directly in callback (outside render)
|
|
const setAuth = useAuthStore.getState().setAuth;
|
|
await setAuth(response.user, response.token);
|
|
},
|
|
});
|
|
}
|
|
```
|
|
|
|
**Why?**
|
|
|
|
- Event handlers run outside React render cycle
|
|
- Don't need to re-render when state changes
|
|
- Using `getState()` directly is cleaner
|
|
- Avoids unnecessary hook rules complexity
|
|
|
|
#### Admin-Only Features
|
|
|
|
```typescript
|
|
import { useAuth } from '@/lib/stores';
|
|
|
|
function AdminPanel() {
|
|
const user = useAuth(state => state.user);
|
|
const isAdmin = user?.is_superuser ?? false;
|
|
|
|
if (!isAdmin) {
|
|
return <AccessDenied />;
|
|
}
|
|
|
|
return <AdminDashboard />;
|
|
}
|
|
```
|
|
|
|
### 6.4 Testing Integration
|
|
|
|
#### Unit Tests (Jest)
|
|
|
|
```typescript
|
|
import { useAuth } from '@/lib/stores';
|
|
|
|
jest.mock('@/lib/stores', () => ({
|
|
useAuth: jest.fn(),
|
|
}));
|
|
|
|
test('renders user name', () => {
|
|
(useAuth as jest.Mock).mockReturnValue({
|
|
user: { first_name: 'John', last_name: 'Doe' },
|
|
isAuthenticated: true,
|
|
});
|
|
|
|
render(<MyComponent />);
|
|
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
});
|
|
```
|
|
|
|
#### E2E Tests (Playwright)
|
|
|
|
```typescript
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('Protected Pages', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
// Inject mock store before navigation
|
|
await page.addInitScript(() => {
|
|
(window as any).__TEST_AUTH_STORE__ = () => ({
|
|
user: { id: '1', email: 'test@example.com', first_name: 'Test', last_name: 'User' },
|
|
accessToken: 'mock-token',
|
|
refreshToken: 'mock-refresh',
|
|
isAuthenticated: true,
|
|
isLoading: false,
|
|
tokenExpiresAt: Date.now() + 900000,
|
|
});
|
|
});
|
|
});
|
|
|
|
test('should display user profile', async ({ page }) => {
|
|
await page.goto('/settings/profile');
|
|
|
|
// No redirect to login - authenticated via mock
|
|
await expect(page).toHaveURL('/settings/profile');
|
|
await expect(page.locator('input[name="email"]')).toHaveValue('test@example.com');
|
|
});
|
|
});
|
|
```
|
|
|
|
### 6.5 Provider Tree Structure
|
|
|
|
**Correct Order** (Critical for Functionality):
|
|
|
|
```typescript
|
|
// src/app/layout.tsx
|
|
<AuthProvider> {/* 1. Provides auth DI layer */}
|
|
<AuthInitializer /> {/* 2. Loads auth from storage (needs AuthProvider) */}
|
|
<Providers> {/* 3. Other providers (Theme, Query) */}
|
|
{children}
|
|
</Providers>
|
|
</AuthProvider>
|
|
```
|
|
|
|
**Why This Order?**
|
|
|
|
- AuthProvider must wrap AuthInitializer (AuthInitializer uses auth state)
|
|
- AuthProvider should wrap all app providers (auth available everywhere)
|
|
- Keep provider tree shallow for performance
|
|
|
|
### 6.6 Token Management Strategy
|
|
|
|
**Two-Token System:**
|
|
|
|
- **Access Token**: Short-lived (15 min), stored in memory/sessionStorage
|
|
- **Refresh Token**: Long-lived (7 days), stored in httpOnly cookie (preferred) or localStorage
|
|
|
|
**Token Storage Decision:**
|
|
|
|
- **Primary**: httpOnly cookies (most secure, prevents XSS)
|
|
- **Fallback**: localStorage with encryption wrapper (if cookies not feasible)
|
|
- **Access Token**: sessionStorage or React state (short-lived, acceptable risk)
|
|
|
|
**Token Rotation:**
|
|
|
|
- On refresh, both tokens are rotated
|
|
- Old refresh token is invalidated immediately
|
|
- Prevents token replay attacks
|
|
|
|
### 6.2 Per-Device Session Tracking
|
|
|
|
Backend tracks sessions per device:
|
|
|
|
- Each login creates a unique session with device info
|
|
- Users can view all active sessions
|
|
- Users can revoke individual sessions
|
|
- Logout only affects current device
|
|
- "Logout All" deactivates all sessions
|
|
|
|
Frontend Implementation:
|
|
|
|
- Session list page at `/settings/sessions`
|
|
- Display device name, IP, location, last used
|
|
- Highlight current session
|
|
- Revoke button for non-current sessions
|
|
|
|
### 6.3 Auth Guard Implementation
|
|
|
|
**Layout-Based Protection:**
|
|
|
|
```typescript
|
|
// app/(authenticated)/layout.tsx
|
|
export default function AuthenticatedLayout({ children }) {
|
|
return (
|
|
<AuthGuard>
|
|
<Header />
|
|
<main>{children}</main>
|
|
<Footer />
|
|
</AuthGuard>
|
|
);
|
|
}
|
|
```
|
|
|
|
**Permission Checks:**
|
|
|
|
```typescript
|
|
// app/(authenticated)/admin/layout.tsx
|
|
export default function AdminLayout({ children }) {
|
|
const { user } = useAuth();
|
|
|
|
if (!user?.is_superuser) {
|
|
redirect('/403');
|
|
}
|
|
|
|
return <AdminLayoutUI>{children}</AdminLayoutUI>;
|
|
}
|
|
```
|
|
|
|
### 6.4 Security Best Practices
|
|
|
|
1. **No tokens in localStorage** (access token in sessionStorage acceptable due to short expiry)
|
|
2. **Always use HTTPS in production**
|
|
3. **Automatic token refresh before expiry** (5 min threshold)
|
|
4. **Clear all auth state on logout**
|
|
5. **Validate token ownership** (backend checks JTI against session)
|
|
6. **Rate limiting awareness** (handle 429 responses)
|
|
7. **CSRF protection** (if not using cookies for main token)
|
|
|
|
---
|
|
|
|
## 7. API Integration
|
|
|
|
### 7.1 OpenAPI Client Generation
|
|
|
|
**Workflow:**
|
|
|
|
```
|
|
Backend OpenAPI Spec → @hey-api/openapi-ts → TypeScript Client
|
|
(/api/v1/openapi.json) (src/lib/api/generated/)
|
|
```
|
|
|
|
**Generation Script** (`scripts/generate-api-client.sh`):
|
|
|
|
```bash
|
|
#!/bin/bash
|
|
API_URL="${NEXT_PUBLIC_API_BASE_URL:-http://localhost:8000}"
|
|
npx @hey-api/openapi-ts \
|
|
--input "$API_URL/api/v1/openapi.json" \
|
|
--output ./src/lib/api/generated \
|
|
--client axios
|
|
```
|
|
|
|
**Benefits:**
|
|
|
|
- Type-safe API calls
|
|
- Auto-completion in IDE
|
|
- Compile-time error checking
|
|
- No manual type definition
|
|
- Always in sync with backend
|
|
|
|
### 7.2 Axios Configuration
|
|
|
|
**Base Instance** (`src/lib/api/client.ts`):
|
|
|
|
```typescript
|
|
export const apiClient = axios.create({
|
|
baseURL: process.env.NEXT_PUBLIC_API_URL,
|
|
timeout: 30000,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
});
|
|
```
|
|
|
|
**Request Interceptor:**
|
|
|
|
```typescript
|
|
apiClient.interceptors.request.use(
|
|
(config) => {
|
|
const token = authStore.getState().accessToken;
|
|
if (token) {
|
|
config.headers.Authorization = `Bearer ${token}`;
|
|
}
|
|
return config;
|
|
},
|
|
(error) => Promise.reject(error)
|
|
);
|
|
```
|
|
|
|
**Response Interceptor:**
|
|
|
|
```typescript
|
|
apiClient.interceptors.response.use(
|
|
(response) => response,
|
|
async (error) => {
|
|
if (error.response?.status === 401) {
|
|
// Try to refresh token
|
|
try {
|
|
await authStore.getState().refreshTokens();
|
|
// Retry original request
|
|
return apiClient.request(error.config);
|
|
} catch {
|
|
// Refresh failed, logout
|
|
authStore.getState().clearAuth();
|
|
window.location.href = '/login';
|
|
}
|
|
}
|
|
return Promise.reject(parseAPIError(error));
|
|
}
|
|
);
|
|
```
|
|
|
|
### 7.3 Error Handling
|
|
|
|
**Backend Error Format:**
|
|
|
|
```typescript
|
|
{
|
|
success: false,
|
|
errors: [
|
|
{
|
|
code: "AUTH_001",
|
|
message: "Invalid credentials",
|
|
field: "email"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Frontend Error Parsing:**
|
|
|
|
```typescript
|
|
export function parseAPIError(error: AxiosError): APIError {
|
|
if (error.response?.data?.errors) {
|
|
return error.response.data.errors;
|
|
}
|
|
return [
|
|
{
|
|
code: 'UNKNOWN',
|
|
message: 'An unexpected error occurred',
|
|
},
|
|
];
|
|
}
|
|
```
|
|
|
|
**Error Code Mapping:**
|
|
|
|
```typescript
|
|
const ERROR_MESSAGES = {
|
|
AUTH_001: 'Invalid email or password',
|
|
USER_002: 'This email is already registered',
|
|
VAL_001: 'Please check your input',
|
|
// ... all backend error codes
|
|
};
|
|
```
|
|
|
|
### 7.4 React Query Hooks Pattern
|
|
|
|
**Standard Pattern:**
|
|
|
|
```typescript
|
|
// lib/api/hooks/useUsers.ts
|
|
export function useUsers(filters?: UserFilters) {
|
|
return useQuery({
|
|
queryKey: ['users', filters],
|
|
queryFn: () => UserService.getUsers(filters),
|
|
});
|
|
}
|
|
|
|
export function useUser(userId: string) {
|
|
return useQuery({
|
|
queryKey: ['users', userId],
|
|
queryFn: () => UserService.getUser(userId),
|
|
enabled: !!userId,
|
|
});
|
|
}
|
|
|
|
export function useUpdateUser() {
|
|
const queryClient = useQueryClient();
|
|
return useMutation({
|
|
mutationFn: ({ id, data }: { id: string; data: UpdateUserDto }) =>
|
|
UserService.updateUser(id, data),
|
|
onSuccess: (_, { id }) => {
|
|
queryClient.invalidateQueries({ queryKey: ['users', id] });
|
|
queryClient.invalidateQueries({ queryKey: ['users'] });
|
|
toast.success('User updated successfully');
|
|
},
|
|
onError: (error: APIError[]) => {
|
|
toast.error(error[0]?.message || 'Failed to update user');
|
|
},
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Routing Strategy
|
|
|
|
### 8.1 App Router Structure
|
|
|
|
```
|
|
app/
|
|
├── (auth)/ # Auth route group (no auth layout)
|
|
│ ├── layout.tsx
|
|
│ ├── login/
|
|
│ └── register/
|
|
├── (authenticated)/ # Protected route group
|
|
│ ├── layout.tsx # Auth guard + header/footer
|
|
│ ├── page.tsx # Home
|
|
│ ├── settings/
|
|
│ │ ├── layout.tsx # Settings sidebar
|
|
│ │ ├── profile/
|
|
│ │ ├── password/
|
|
│ │ └── sessions/
|
|
│ └── admin/
|
|
│ ├── layout.tsx # Admin sidebar + permission check
|
|
│ ├── users/
|
|
│ └── organizations/
|
|
├── dev/ # Development-only routes
|
|
│ ├── layout.tsx # NODE_ENV check
|
|
│ └── components/
|
|
├── layout.tsx # Root layout
|
|
└── page.tsx # Public home
|
|
```
|
|
|
|
**Route Groups** (parentheses in folder name):
|
|
|
|
- Organize routes without affecting URL
|
|
- Apply different layouts to route subsets
|
|
- Example: `(auth)` and `(authenticated)` have different layouts
|
|
|
|
### 8.2 Layout Strategy
|
|
|
|
**Root Layout** (`app/layout.tsx`):
|
|
|
|
- HTML structure
|
|
- React Query provider
|
|
- Theme provider
|
|
- Global metadata
|
|
|
|
**Auth Layout** (`app/(auth)/layout.tsx`):
|
|
|
|
- Centered form container
|
|
- No header/footer
|
|
- Minimal styling
|
|
|
|
**Authenticated Layout** (`app/(authenticated)/layout.tsx`):
|
|
|
|
- Auth guard (redirect if not authenticated)
|
|
- Header with user menu
|
|
- Main content area
|
|
- Footer
|
|
|
|
**Admin Layout** (`app/(authenticated)/admin/layout.tsx`):
|
|
|
|
- Admin sidebar
|
|
- Breadcrumbs
|
|
- Admin permission check (is_superuser)
|
|
|
|
### 8.3 Loading & Error States
|
|
|
|
```
|
|
app/(authenticated)/admin/users/
|
|
├── page.tsx # Main page
|
|
├── loading.tsx # Streaming UI / Suspense fallback
|
|
└── error.tsx # Error boundary
|
|
```
|
|
|
|
**loading.tsx**: Displayed while page/component is loading
|
|
**error.tsx**: Displayed when error occurs (with retry button)
|
|
|
|
---
|
|
|
|
## 9. Component Organization
|
|
|
|
### 9.1 Directory Structure
|
|
|
|
```
|
|
components/
|
|
├── ui/ # shadcn components (copy-paste)
|
|
│ ├── button.tsx
|
|
│ ├── card.tsx
|
|
│ └── ...
|
|
├── auth/ # Authentication components
|
|
│ ├── LoginForm.tsx
|
|
│ ├── RegisterForm.tsx
|
|
│ └── AuthGuard.tsx
|
|
├── admin/ # Admin-specific components
|
|
│ ├── UserTable.tsx
|
|
│ ├── UserForm.tsx
|
|
│ ├── BulkActionBar.tsx
|
|
│ └── ...
|
|
├── settings/ # Settings page components
|
|
│ ├── ProfileSettings.tsx
|
|
│ ├── SessionManagement.tsx
|
|
│ └── ...
|
|
├── charts/ # Chart wrappers
|
|
│ ├── BarChartCard.tsx
|
|
│ └── ...
|
|
├── layout/ # Layout components
|
|
│ ├── Header.tsx
|
|
│ ├── Sidebar.tsx
|
|
│ └── ...
|
|
└── common/ # Reusable components
|
|
├── DataTable.tsx
|
|
├── LoadingSpinner.tsx
|
|
└── ...
|
|
```
|
|
|
|
### 9.2 Component Guidelines
|
|
|
|
**Naming:**
|
|
|
|
- PascalCase for components: `UserTable.tsx`
|
|
- Match file name with component name
|
|
- One component per file
|
|
|
|
**Structure:**
|
|
|
|
```typescript
|
|
// 1. Imports
|
|
import { useState } from 'react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { useUsers } from '@/lib/api/hooks/useUsers';
|
|
|
|
// 2. Types
|
|
interface UserTableProps {
|
|
filters?: UserFilters;
|
|
}
|
|
|
|
// 3. Component
|
|
export function UserTable({ filters }: UserTableProps) {
|
|
// Hooks
|
|
const { data, isLoading } = useUsers(filters);
|
|
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
|
|
|
// Derived state
|
|
const hasSelection = selectedIds.length > 0;
|
|
|
|
// Event handlers
|
|
const handleSelectAll = () => {
|
|
setSelectedIds(data?.map(u => u.id) || []);
|
|
};
|
|
|
|
// Render
|
|
if (isLoading) return <LoadingSpinner />;
|
|
|
|
return (
|
|
<div>
|
|
{/* JSX */}
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
**Best Practices:**
|
|
|
|
- Prefer named exports over default exports
|
|
- Destructure props in function signature
|
|
- Extract complex logic to hooks
|
|
- Keep components focused (single responsibility)
|
|
- Use composition over prop drilling
|
|
|
|
### 9.3 Styling Strategy
|
|
|
|
**Tailwind Utility Classes:**
|
|
|
|
```typescript
|
|
<button className="px-4 py-2 bg-primary text-white rounded-md hover:bg-primary/90">
|
|
Click Me
|
|
</button>
|
|
```
|
|
|
|
**Conditional Classes with cn():**
|
|
|
|
```typescript
|
|
import { cn } from '@/lib/utils/cn';
|
|
|
|
<div className={cn(
|
|
"base-classes",
|
|
isActive && "active-classes",
|
|
className // Allow override from props
|
|
)} />
|
|
```
|
|
|
|
**Dark Mode:**
|
|
|
|
```typescript
|
|
<div className="bg-white dark:bg-gray-900 text-black dark:text-white">
|
|
Content
|
|
</div>
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Testing Strategy
|
|
|
|
### 10.1 Testing Pyramid
|
|
|
|
```
|
|
┌─────────┐
|
|
/ E2E Tests \ (10% - Critical flows)
|
|
/ \
|
|
/_________________\
|
|
/ \
|
|
/ Integration Tests \ (30% - Component + API)
|
|
/ \
|
|
/_________________________\
|
|
/ \
|
|
/ Unit Tests \ (60% - Hooks, Utils, Libs)
|
|
/_______________________________\
|
|
```
|
|
|
|
### 10.2 Test Categories
|
|
|
|
**Unit Tests** (60% of suite):
|
|
|
|
- Utilities (`lib/utils/`)
|
|
- Custom hooks (`hooks/`)
|
|
- Services (`services/`)
|
|
- Pure functions
|
|
|
|
**Component Tests** (30% of suite):
|
|
|
|
- Reusable components (`components/`)
|
|
- Forms with validation
|
|
- User interactions
|
|
- Accessibility
|
|
|
|
**Integration Tests** (E2E with Playwright, 10% of suite):
|
|
|
|
- Critical user flows:
|
|
- Login → Dashboard
|
|
- Admin: Create/Edit/Delete User
|
|
- Admin: Manage Organizations
|
|
- Session Management
|
|
- Multi-page journeys
|
|
- Real backend interaction (or mock server)
|
|
|
|
### 10.3 Testing Tools
|
|
|
|
**Jest + React Testing Library:**
|
|
|
|
```typescript
|
|
// UserTable.test.tsx
|
|
import { render, screen } from '@testing-library/react';
|
|
import { UserTable } from './UserTable';
|
|
|
|
test('renders user table with data', async () => {
|
|
render(<UserTable />);
|
|
expect(await screen.findByText('John Doe')).toBeInTheDocument();
|
|
});
|
|
```
|
|
|
|
**Playwright E2E:**
|
|
|
|
```typescript
|
|
// tests/e2e/auth.spec.ts
|
|
test('user can login', async ({ page }) => {
|
|
await page.goto('/login');
|
|
await page.fill('[name="email"]', 'admin@example.com');
|
|
await page.fill('[name="password"]', 'password123');
|
|
await page.click('button[type="submit"]');
|
|
await expect(page).toHaveURL('/dashboard');
|
|
});
|
|
```
|
|
|
|
### 10.4 Coverage Target
|
|
|
|
**Goal: 90%+ Overall Coverage**
|
|
|
|
- Unit tests: 95%+
|
|
- Component tests: 85%+
|
|
- Integration tests: Critical paths only
|
|
|
|
**Justification for 90%:**
|
|
|
|
- This is a template for production projects
|
|
- High coverage ensures robustness
|
|
- Confidence for extension and customization
|
|
|
|
---
|
|
|
|
## 11. Performance Considerations
|
|
|
|
### 11.1 Optimization Strategies
|
|
|
|
**Code Splitting:**
|
|
|
|
```typescript
|
|
// Dynamic imports for heavy components
|
|
const AdminDashboard = dynamic(() => import('./AdminDashboard'), {
|
|
loading: () => <LoadingSpinner />,
|
|
});
|
|
```
|
|
|
|
**Image Optimization:**
|
|
|
|
```typescript
|
|
import Image from 'next/image';
|
|
|
|
<Image
|
|
src="/user-avatar.jpg"
|
|
alt="User"
|
|
width={40}
|
|
height={40}
|
|
loading="lazy"
|
|
/>
|
|
```
|
|
|
|
**React Query Caching:**
|
|
|
|
- Stale time: 1 minute (reduce unnecessary refetches)
|
|
- Cache time: 5 minutes (keep data in memory)
|
|
- Background refetch: Yes (keep data fresh)
|
|
|
|
**Bundle Size Monitoring:**
|
|
|
|
```bash
|
|
npm run build && npm run analyze
|
|
# Use webpack-bundle-analyzer to identify large dependencies
|
|
```
|
|
|
|
### 11.2 Performance Targets
|
|
|
|
**Lighthouse Scores:**
|
|
|
|
- Performance: >90
|
|
- Accessibility: 100
|
|
- Best Practices: >90
|
|
- SEO: >90
|
|
|
|
**Core Web Vitals:**
|
|
|
|
- LCP (Largest Contentful Paint): <2.5s
|
|
- FID (First Input Delay): <100ms
|
|
- CLS (Cumulative Layout Shift): <0.1
|
|
|
|
---
|
|
|
|
## 12. Security Architecture
|
|
|
|
### 12.1 Client-Side Security
|
|
|
|
**XSS Prevention:**
|
|
|
|
- React's default escaping (JSX)
|
|
- Sanitize user input if rendering HTML
|
|
- CSP headers (configured in backend)
|
|
|
|
**Token Security:**
|
|
|
|
- Access token: sessionStorage or memory (15 min expiry mitigates risk)
|
|
- Refresh token: httpOnly cookie (preferred) or encrypted localStorage
|
|
- Never log tokens to console in production
|
|
|
|
**HTTPS Only:**
|
|
|
|
- All production requests over HTTPS
|
|
- Cookies with Secure flag
|
|
- No mixed content
|
|
|
|
### 12.2 Input Validation
|
|
|
|
**Client-Side Validation:**
|
|
|
|
- Zod schemas for all forms
|
|
- Immediate feedback to users
|
|
- Prevent malformed requests
|
|
|
|
**Remember:**
|
|
|
|
- Client validation is for UX
|
|
- Backend validation is for security
|
|
- Always trust backend, not client
|
|
|
|
### 12.3 Dependency Security
|
|
|
|
**Regular Audits:**
|
|
|
|
```bash
|
|
npm audit
|
|
npm audit fix
|
|
```
|
|
|
|
**Automated Scanning:**
|
|
|
|
- Dependabot (GitHub)
|
|
- Snyk (CI/CD integration)
|
|
|
|
---
|
|
|
|
## 13. Design Decisions & Rationale
|
|
|
|
### 13.1 Why Next.js App Router?
|
|
|
|
**Pros:**
|
|
|
|
- Server Components reduce client bundle
|
|
- Better data fetching patterns
|
|
- Streaming and Suspense built-in
|
|
- Simpler layouts and error handling
|
|
|
|
**Cons:**
|
|
|
|
- Newer, less mature than Pages Router
|
|
- Learning curve for team
|
|
|
|
**Decision:** App Router is the future, worth the investment
|
|
|
|
### 13.2 Why TanStack Query?
|
|
|
|
**Alternatives Considered:**
|
|
|
|
- SWR: Similar but less features
|
|
- Redux Toolkit Query: Too much boilerplate for our use case
|
|
- Apollo Client: Overkill for REST API
|
|
|
|
**Why TanStack Query:**
|
|
|
|
- Best-in-class caching and refetching
|
|
- Framework-agnostic (not tied to Next.js)
|
|
- Excellent DevTools
|
|
- Optimistic updates out of the box
|
|
|
|
### 13.3 Why Zustand over Redux?
|
|
|
|
**Why NOT Redux:**
|
|
|
|
- Too much boilerplate (actions, reducers, middleware)
|
|
- We don't need time-travel debugging
|
|
- Most state is server state (handled by React Query)
|
|
|
|
**Why Zustand:**
|
|
|
|
- Minimal API (easy to learn)
|
|
- No Context API overhead
|
|
- Can use outside React (interceptors)
|
|
- Only ~1KB
|
|
|
|
### 13.4 Why shadcn/ui over Component Libraries?
|
|
|
|
**Alternatives Considered:**
|
|
|
|
- Material-UI: Heavy, opinionated styling
|
|
- Chakra UI: Good, but still an npm dependency
|
|
- Ant Design: Too opinionated for template
|
|
|
|
**Why shadcn/ui:**
|
|
|
|
- Copy-paste (full control)
|
|
- Accessible (Radix UI primitives)
|
|
- Tailwind-based (consistent with our stack)
|
|
- Customizable without ejecting
|
|
|
|
### 13.5 Why Axios over Fetch?
|
|
|
|
**Why NOT Fetch:**
|
|
|
|
- No request/response interceptors
|
|
- Manual timeout handling
|
|
- Less ergonomic error handling
|
|
|
|
**Why Axios:**
|
|
|
|
- Interceptors (essential for auth)
|
|
- Automatic JSON parsing
|
|
- Better error handling
|
|
- Request cancellation
|
|
- Timeout configuration
|
|
|
|
### 13.6 Token Storage Strategy
|
|
|
|
**Decision: httpOnly Cookies (Primary), localStorage (Fallback)**
|
|
|
|
**Why httpOnly Cookies:**
|
|
|
|
- Most secure (not accessible to JavaScript)
|
|
- Prevents XSS token theft
|
|
- Automatic sending with requests (if CORS configured)
|
|
|
|
**Why Fallback to localStorage:**
|
|
|
|
- Simpler initial setup (no backend cookie handling)
|
|
- Still secure with proper measures:
|
|
- Short access token expiry (15 min)
|
|
- Token rotation on refresh
|
|
- HTTPS only
|
|
- Encrypted wrapper (optional)
|
|
|
|
**Implementation:**
|
|
|
|
- Try httpOnly cookies first
|
|
- Fall back to localStorage if not feasible
|
|
- Document choice in code
|
|
|
|
---
|
|
|
|
## 14. Deployment Architecture
|
|
|
|
### 14.1 Production Deployment
|
|
|
|
**Recommended Platform: Vercel**
|
|
|
|
- Native Next.js support
|
|
- Edge functions for middleware
|
|
- Automatic preview deployments
|
|
- CDN with global edge network
|
|
|
|
**Alternative: Docker**
|
|
|
|
```dockerfile
|
|
FROM node:20-alpine
|
|
WORKDIR /app
|
|
COPY package*.json ./
|
|
RUN npm ci --only=production
|
|
COPY . .
|
|
RUN npm run build
|
|
EXPOSE 3000
|
|
CMD ["npm", "start"]
|
|
```
|
|
|
|
### 14.2 Environment Configuration
|
|
|
|
**Development:**
|
|
|
|
```env
|
|
NEXT_PUBLIC_API_URL=http://localhost:8000/api/v1
|
|
NODE_ENV=development
|
|
```
|
|
|
|
**Production:**
|
|
|
|
```env
|
|
NEXT_PUBLIC_API_URL=https://api.example.com/api/v1
|
|
NODE_ENV=production
|
|
```
|
|
|
|
**Secrets:**
|
|
|
|
- Never commit `.env.local`
|
|
- Use platform-specific secret management (Vercel Secrets, Docker Secrets)
|
|
|
|
### 14.3 CI/CD Pipeline
|
|
|
|
```yaml
|
|
# .github/workflows/ci.yml
|
|
name: CI
|
|
on: [push, pull_request]
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
- name: Run tests
|
|
run: npm test
|
|
- name: Run linter
|
|
run: npm run lint
|
|
- name: Type check
|
|
run: npm run type-check
|
|
- name: Build
|
|
run: npm run build
|
|
```
|
|
|
|
---
|
|
|
|
## Conclusion
|
|
|
|
This architecture document provides a comprehensive overview of the frontend system design, patterns, and decisions. It should serve as a reference for developers working on the project and guide future architectural decisions.
|
|
|
|
For specific implementation details, refer to:
|
|
|
|
- **CODING_STANDARDS.md**: Code style and conventions
|
|
- **COMPONENT_GUIDE.md**: Component usage and patterns
|
|
- **FEATURE_EXAMPLES.md**: Step-by-step feature implementation
|
|
- **API_INTEGRATION.md**: Detailed API integration guide
|
|
|
|
**Remember**: This is a living document. Update it as the architecture evolves.
|