# Frontend Coding Standards **Project**: Next.js + FastAPI Template **Version**: 1.0 **Last Updated**: 2025-10-31 **Status**: Enforced --- ## Table of Contents 1. [TypeScript Standards](#1-typescript-standards) 2. [React Component Standards](#2-react-component-standards) 3. [Naming Conventions](#3-naming-conventions) 4. [File Organization](#4-file-organization) 5. [State Management](#5-state-management) 6. [API Integration](#6-api-integration) 7. [Form Handling](#7-form-handling) 8. [Styling Standards](#8-styling-standards) 9. [Error Handling](#9-error-handling) 10. [Testing Standards](#10-testing-standards) 11. [Accessibility Standards](#11-accessibility-standards) 12. [Performance Best Practices](#12-performance-best-practices) 13. [Security Best Practices](#13-security-best-practices) 14. [Code Review Checklist](#14-code-review-checklist) --- ## 1. TypeScript Standards ### 1.1 General Principles **✅ DO:** - Enable strict mode in `tsconfig.json` - Define explicit types for all function parameters and return values - Use TypeScript's type inference where obvious - Prefer `interface` for object shapes, `type` for unions/primitives - Use generics for reusable, type-safe components and functions **❌ DON'T:** - Use `any` (use `unknown` if type is truly unknown) - Use `as any` casts (refactor to proper types) - Use `@ts-ignore` or `@ts-nocheck` (fix the underlying issue) - Leave implicit `any` types ### 1.2 Interfaces vs Types **Use `interface` for object shapes:** ```typescript // ✅ Good interface User { id: string; name: string; email: string; isActive: boolean; } interface UserFormProps { user?: User; onSubmit: (data: User) => void; isLoading?: boolean; } ``` **Use `type` for unions, intersections, and primitives:** ```typescript // ✅ Good type UserRole = 'admin' | 'user' | 'guest'; type UserId = string; type UserOrNull = User | null; type UserWithPermissions = User & { permissions: string[] }; ``` ### 1.3 Function Signatures **Always type function parameters and return values:** ```typescript // ✅ Good function formatUserName(user: User): string { return `${user.firstName} ${user.lastName}`; } async function fetchUser(id: string): Promise { const response = await apiClient.get(`/users/${id}`); return response.data; } // ❌ Bad function formatUserName(user) { // Implicit any return `${user.firstName} ${user.lastName}`; } ``` ### 1.4 Generics **Use generics for reusable, type-safe code:** ```typescript // ✅ Good: Generic data table interface DataTableProps { data: T[]; columns: Column[]; onRowClick?: (row: T) => void; } export function DataTable({ data, columns, onRowClick }: DataTableProps) { // Implementation } // Usage is fully type-safe data={users} columns={userColumns} /> ``` ### 1.5 Unknown vs Any **Use `unknown` for truly unknown types:** ```typescript // ✅ Good: Force type checking function parseJSON(jsonString: string): unknown { return JSON.parse(jsonString); } const result = parseJSON('{"name": "John"}'); // Must narrow type before use if (typeof result === 'object' && result !== null && 'name' in result) { console.log(result.name); } // ❌ Bad: Bypasses all type checking function parseJSON(jsonString: string): any { return JSON.parse(jsonString); } ``` ### 1.6 Type Guards **Create type guards for runtime type checking:** ```typescript // ✅ Good function isUser(value: unknown): value is User { return typeof value === 'object' && value !== null && 'id' in value && 'email' in value; } // Usage if (isUser(data)) { // TypeScript knows data is User here console.log(data.email); } ``` ### 1.7 Utility Types **Use TypeScript utility types:** ```typescript // Partial - make all properties optional type UserUpdate = Partial; // Pick - select specific properties type UserPreview = Pick; // Omit - exclude specific properties type UserWithoutPassword = Omit; // Record - create object type with specific keys type UserRolePermissions = Record; // Extract - extract from union type AdminRole = Extract; // Exclude - exclude from union type NonAdminRole = Exclude; ``` --- ## 2. React Component Standards ### 2.1 Component Structure **Standard component template:** ```typescript // 1. Imports (React, external libs, internal, types, styles) 'use client'; // If client component import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { Button } from '@/components/ui/button'; import { useUsers } from '@/lib/api/hooks/useUsers'; // 2. Types interface UserListProps { initialFilters?: UserFilters; onUserSelect?: (user: User) => void; className?: string; } // 3. Component export function UserList({ initialFilters, onUserSelect, className }: UserListProps) { // 3a. Hooks (state, effects, router, query) const router = useRouter(); const [filters, setFilters] = useState(initialFilters); const { data, isLoading, error } = useUsers(filters); // 3b. Derived state const hasUsers = data && data.length > 0; const isEmpty = !isLoading && !hasUsers; // 3c. Effects useEffect(() => { // Side effects }, [dependencies]); // 3d. Event handlers const handleUserClick = (user: User) => { onUserSelect?.(user); }; // 3e. Render conditions if (isLoading) return ; if (error) return ; if (isEmpty) return ; // 3f. Main render return (
{/* JSX */}
); } ``` ### 2.2 Server Components vs Client Components **Server Components by default:** ```typescript // ✅ Good: Server Component (default) // app/(authenticated)/users/page.tsx export default async function UsersPage() { // Could fetch data here, but we use client components with React Query return (
); } ``` **Client Components only when needed:** ```typescript // ✅ Good: Client Component (interactive) 'use client'; import { useState } from 'react'; import { useUsers } from '@/lib/api/hooks/useUsers'; export function UserList() { const [search, setSearch] = useState(''); const { data } = useUsers({ search }); return (
setSearch(e.target.value)} /> {/* Render users */}
); } ``` **When to use Client Components:** - Using React hooks (useState, useEffect, etc.) - Event handlers (onClick, onChange, etc.) - Browser APIs (window, document, localStorage) - React Context consumers ### 2.3 Props **Always type props explicitly:** ```typescript // ✅ Good: Explicit interface interface ButtonProps { label: string; onClick: () => void; variant?: 'primary' | 'secondary'; disabled?: boolean; className?: string; children?: React.ReactNode; } export function Button({ label, onClick, variant = 'primary', disabled = false, className, }: ButtonProps) { // Implementation } ``` **Destructure props in function signature:** ```typescript // ✅ Good function UserCard({ user, onEdit }: UserCardProps) { return
{user.name}
; } // ❌ Bad function UserCard(props: UserCardProps) { return
{props.user.name}
; } ``` **Allow className override:** ```typescript // ✅ Good: Allow consumers to add classes interface CardProps { title: string; className?: string; } export function Card({ title, className }: CardProps) { return (
{title}
); } ``` ### 2.4 Composition Over Prop Drilling **Use composition patterns:** ```typescript // ✅ Good: Composition Users Manage system users // ❌ Bad: Complex props } footerAction={} /> ``` ### 2.5 Named Exports vs Default Exports **Prefer named exports:** ```typescript // ✅ Good: Named export export function UserList() { // Implementation } // Easier to refactor, better IDE support, explicit imports import { UserList } from './UserList'; // ❌ Avoid: Default export export default function UserList() { // Implementation } // Can be renamed on import, less explicit import WhateverName from './UserList'; ``` **Exception: Next.js pages must use default export** ```typescript // pages and route handlers require default export export default function UsersPage() { return
Users
; } ``` ### 2.6 Custom Hooks **Extract reusable logic:** ```typescript // ✅ Good: Custom hook function useDebounce(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); return () => { clearTimeout(handler); }; }, [value, delay]); return debouncedValue; } // Usage function SearchInput() { const [search, setSearch] = useState(''); const debouncedSearch = useDebounce(search, 500); const { data } = useUsers({ search: debouncedSearch }); return setSearch(e.target.value)} />; } ``` **Hook naming: Always prefix with "use":** ```typescript // ✅ Good function useAuth() {} function useUsers() {} function useDebounce() {} // ❌ Bad function auth() {} function getUsers() {} ``` --- ## 3. Naming Conventions ### 3.1 Files and Directories | Type | Convention | Example | | --------------- | ------------------------------- | --------------------------------------- | | Components | PascalCase | `UserTable.tsx`, `LoginForm.tsx` | | Hooks | camelCase with `use` prefix | `useAuth.ts`, `useDebounce.ts` | | Utilities | camelCase | `formatDate.ts`, `parseError.ts` | | Types | camelCase | `user.ts`, `api.ts` | | Constants | camelCase or UPPER_SNAKE_CASE | `constants.ts`, `API_ENDPOINTS.ts` | | Stores | camelCase with `Store` suffix | `authStore.ts`, `uiStore.ts` | | Services | camelCase with `Service` suffix | `authService.ts`, `adminService.ts` | | Pages (Next.js) | lowercase | `page.tsx`, `layout.tsx`, `loading.tsx` | ### 3.2 Variables and Functions **Variables:** ```typescript // ✅ Good: camelCase const userName = 'John'; const isAuthenticated = true; const userList = []; // ❌ Bad const UserName = 'John'; // PascalCase for variable const user_name = 'John'; // snake_case ``` **Functions:** ```typescript // ✅ Good: camelCase, descriptive verb + noun function getUserById(id: string): User {} function formatDate(date: Date): string {} function handleSubmit(data: FormData): void {} // ❌ Bad function User(id: string) {} // Looks like a class function get_user(id: string) {} // snake_case function gub(id: string) {} // Not descriptive ``` **Event Handlers:** ```typescript // ✅ Good: handle + EventName const handleClick = () => {}; const handleSubmit = () => {}; const handleInputChange = () => {}; // ❌ Bad const onClick = () => {}; // Confusing with prop name const submit = () => {}; ``` **Boolean Variables:** ```typescript // ✅ Good: is/has/should prefix const isLoading = true; const hasError = false; const shouldRedirect = true; const canEdit = false; // ❌ Bad const loading = true; const error = false; ``` ### 3.3 Constants **Use UPPER_SNAKE_CASE for true constants:** ```typescript // ✅ Good const MAX_RETRY_ATTEMPTS = 3; const API_BASE_URL = 'https://api.example.com'; const DEFAULT_PAGE_SIZE = 20; // Constants object const USER_ROLES = { ADMIN: 'admin', USER: 'user', GUEST: 'guest', } as const; ``` ### 3.4 Types and Interfaces **PascalCase for types and interfaces:** ```typescript // ✅ Good interface User {} interface UserFormProps {} type UserId = string; type UserRole = 'admin' | 'user'; // ❌ Bad interface user {} interface user_form_props {} type userId = string; ``` --- ## 4. File Organization ### 4.1 Import Order **Organize imports in this order:** ```typescript // 1. React and Next.js import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import Image from 'next/image'; // 2. External libraries import { useQuery } from '@tanstack/react-query'; import { toast } from 'sonner'; import { format } from 'date-fns'; // 3. Internal components (UI first, then features) import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { UserTable } from '@/components/admin/UserTable'; // 4. Internal hooks and utilities import { useAuth } from '@/hooks/useAuth'; import { useUsers } from '@/lib/api/hooks/useUsers'; import { cn } from '@/lib/utils/cn'; // 5. Types import type { User, UserFilters } from '@/types/user'; // 6. Styles (if any) import styles from './Component.module.css'; ``` ### 4.2 Co-location **Group related files together:** ``` components/admin/ ├── UserTable/ │ ├── UserTable.tsx │ ├── UserTable.test.tsx │ ├── UserTableRow.tsx │ ├── UserTableFilters.tsx │ └── index.ts (re-export) ``` **Or flat structure for simpler components:** ``` components/admin/ ├── UserTable.tsx ├── UserTable.test.tsx ├── UserForm.tsx ├── UserForm.test.tsx ``` ### 4.3 Barrel Exports **Use index.ts for clean imports:** ```typescript // components/ui/index.ts export { Button } from './button'; export { Card, CardHeader, CardContent } from './card'; export { Input } from './input'; // Usage import { Button, Card, Input } from '@/components/ui'; ``` --- ## 5. State Management ### 5.1 State Placement **Keep state as local as possible:** ```typescript // ✅ Good: Local state function UserFilter() { const [search, setSearch] = useState(''); return setSearch(e.target.value)} />; } // ❌ Bad: Unnecessary global state // Don't put search in Zustand store ``` ### 5.2 TanStack Query Usage **Standard query pattern:** ```typescript // lib/api/hooks/useUsers.ts export function useUsers(filters?: UserFilters) { return useQuery({ queryKey: ['users', filters], queryFn: () => UserService.getUsers(filters), staleTime: 60000, // 1 minute }); } // Component usage function UserList() { const { data, isLoading, error, refetch } = useUsers({ search: 'john' }); if (isLoading) return ; if (error) return ; return
{/* Render data */}
; } ``` **Standard mutation pattern:** ```typescript // lib/api/hooks/useUsers.ts export function useUpdateUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }: { id: string; data: UpdateUserDto }) => UserService.updateUser(id, data), onSuccess: (_, { id }) => { // Invalidate queries to trigger refetch 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'); }, }); } // Component usage function UserForm({ userId }: { userId: string }) { const updateUser = useUpdateUser(); const handleSubmit = (data: UpdateUserDto) => { updateUser.mutate({ id: userId, data }); }; return (
{/* Form fields */}
); } ``` **Query key structure:** ```typescript // ✅ Good: Consistent query keys ['users'][('users', userId)][('users', { search: 'john', page: 1 })][ // List all // Single user // Filtered list ('organizations', orgId, 'members') ][ // Nested resource // ❌ Bad: Inconsistent 'userList' ]['user-' + userId][('getUsersBySearch', 'john')]; ``` ### 5.3 Zustand Store Pattern **Auth store example:** ```typescript // stores/authStore.ts import { create } from 'zustand'; import { persist } from 'zustand/middleware'; interface AuthStore { user: User | null; accessToken: string | null; isAuthenticated: boolean; setUser: (user: User | null) => void; setTokens: (accessToken: string, refreshToken: string) => void; clearAuth: () => void; } export const useAuthStore = create()( persist( (set) => ({ user: null, accessToken: null, isAuthenticated: false, setUser: (user) => set({ user, isAuthenticated: !!user }), setTokens: (accessToken, refreshToken) => { set({ accessToken }); // Store refresh token separately (localStorage or cookie) localStorage.setItem('refreshToken', refreshToken); }, clearAuth: () => { set({ user: null, accessToken: null, isAuthenticated: false }); localStorage.removeItem('refreshToken'); }, }), { name: 'auth-storage', partialize: (state) => ({ user: state.user }), // Only persist user } ) ); // Usage with selector (performance optimization) function UserAvatar() { const user = useAuthStore((state) => state.user); return ; } ``` **UI store example:** ```typescript // stores/uiStore.ts interface UIStore { sidebarOpen: boolean; theme: 'light' | 'dark' | 'system'; setSidebarOpen: (open: boolean) => void; toggleSidebar: () => void; setTheme: (theme: 'light' | 'dark' | 'system') => void; } export const useUIStore = create()( persist( (set) => ({ sidebarOpen: true, theme: 'system', setSidebarOpen: (open) => set({ sidebarOpen: open }), toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })), setTheme: (theme) => set({ theme }), }), { name: 'ui-storage' } ) ); ``` --- ## 6. API Integration ### 6.1 API Client Structure **Axios instance configuration:** ```typescript // lib/api/client.ts import axios from 'axios'; import { useAuthStore } from '@/stores/authStore'; export const apiClient = axios.create({ baseURL: process.env.NEXT_PUBLIC_API_URL, timeout: 30000, headers: { 'Content-Type': 'application/json', }, }); // Request interceptor apiClient.interceptors.request.use( (config) => { const token = useAuthStore.getState().accessToken; if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => Promise.reject(error) ); // Response interceptor apiClient.interceptors.response.use( (response) => response, async (error) => { if (error.response?.status === 401) { // Handle token refresh try { await refreshToken(); return apiClient.request(error.config); } catch { useAuthStore.getState().clearAuth(); window.location.href = '/login'; } } return Promise.reject(parseAPIError(error)); } ); ``` ### 6.2 Error Handling **Parse API errors:** ```typescript // lib/api/errors.ts export interface APIError { code: string; message: string; field?: string; } export function parseAPIError(error: AxiosError): APIError[] { if (error.response?.data?.errors) { return error.response.data.errors; } return [ { code: 'UNKNOWN', message: error.message || 'An unexpected error occurred', }, ]; } // Error code mapping export const ERROR_MESSAGES: Record = { AUTH_001: 'Invalid email or password', USER_002: 'This email is already registered', VAL_001: 'Please check your input', ORG_001: 'Organization name already exists', }; export function getErrorMessage(code: string): string { return ERROR_MESSAGES[code] || 'An error occurred'; } ``` ### 6.3 Hook Organization **One hook file per resource:** ```typescript // lib/api/hooks/useUsers.ts export function useUsers(filters?: UserFilters) {} export function useUser(userId: string) {} export function useCreateUser() {} export function useUpdateUser() {} export function useDeleteUser() {} // lib/api/hooks/useOrganizations.ts export function useOrganizations() {} export function useOrganization(orgId: string) {} export function useCreateOrganization() {} // ... ``` --- ## 7. Form Handling ### 7.1 Form Pattern with react-hook-form + Zod **Standard form implementation:** ```typescript // components/auth/LoginForm.tsx 'use client'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; // 1. Define schema const loginSchema = z.object({ email: z.string().email('Invalid email address'), password: z.string().min(8, 'Password must be at least 8 characters'), }); type LoginFormData = z.infer; // 2. Component export function LoginForm() { const form = useForm({ resolver: zodResolver(loginSchema), defaultValues: { email: '', password: '', }, }); // 3. Submit handler const onSubmit = async (data: LoginFormData) => { try { await authService.login(data); router.push('/dashboard'); } catch (error) { form.setError('root', { message: 'Invalid credentials', }); } }; // 4. Render return (
{form.formState.errors.root && ( {form.formState.errors.root.message} )}
); } ``` ### 7.2 Form Validation **Complex validation with Zod:** ```typescript const userSchema = z .object({ email: z.string().email('Invalid email'), password: z .string() .min(8, 'Min 8 characters') .regex(/[A-Z]/, 'Must contain uppercase') .regex(/[0-9]/, 'Must contain number'), confirmPassword: z.string(), firstName: z.string().min(1, 'Required'), lastName: z.string().optional(), phoneNumber: z .string() .regex(/^\+?[1-9]\d{1,14}$/, 'Invalid phone number') .optional(), }) .refine((data) => data.password === data.confirmPassword, { message: 'Passwords do not match', path: ['confirmPassword'], }); ``` ### 7.3 Form Accessibility **Always include labels and error messages:** ```typescript
{form.formState.errors.email && ( {form.formState.errors.email.message} )}
``` --- ## 8. Styling Standards ### 8.1 Tailwind CSS Usage **Use utility classes:** ```typescript // ✅ Good // ❌ Bad: Inline styles ``` **Use cn() for conditional classes:** ```typescript import { cn } from '@/lib/utils/cn';
``` ### 8.2 Responsive Design **Mobile-first approach:** ```typescript
Content
``` ### 8.3 Dark Mode **Use dark mode classes:** ```typescript
Content
``` --- ## 10. Testing Standards ### 10.1 Test File Organization ``` src/ ├── components/ │ ├── UserTable.tsx │ └── UserTable.test.tsx └── lib/ ├── utils/ │ ├── formatDate.ts │ └── formatDate.test.ts ``` ### 10.2 Test Naming **Use descriptive test names:** ```typescript // ✅ Good test('displays user list when data is loaded', async () => {}); test('shows loading spinner while fetching users', () => {}); test('displays error message when API request fails', () => {}); test('redirects to login when user is not authenticated', () => {}); // ❌ Bad test('works', () => {}); test('test1', () => {}); test('renders', () => {}); ``` ### 10.3 Component Testing **Test user interactions, not implementation:** ```typescript // UserTable.test.tsx import { render, screen, userEvent } from '@testing-library/react'; import { UserTable } from './UserTable'; test('allows user to search for users', async () => { const user = userEvent.setup(); render(); const searchInput = screen.getByPlaceholderText('Search users'); await user.type(searchInput, 'john'); expect(await screen.findByText('John Doe')).toBeInTheDocument(); expect(screen.queryByText('Jane Smith')).not.toBeInTheDocument(); }); ``` ### 10.4 Accessibility Testing **Test with accessibility queries:** ```typescript // Prefer getByRole over getByTestId const button = screen.getByRole('button', { name: 'Submit' }); const heading = screen.getByRole('heading', { name: 'Users' }); const textbox = screen.getByRole('textbox', { name: 'Email' }); ``` --- ## 11. Accessibility Standards ### 11.1 Semantic HTML ```typescript // ✅ Good: Semantic

Title

Content

...
// ❌ Bad: Div soup
Home
``` ### 11.2 ARIA Labels **Use ARIA when semantic HTML isn't enough:** ```typescript
Loading...
``` ### 11.3 Keyboard Navigation **Ensure all interactive elements are keyboard accessible:** ```typescript
{ if (e.key === 'Enter' || e.key === ' ') { handleClick(); } }} > Click me
``` --- ## 12. Performance Best Practices ### 12.1 Code Splitting **Dynamic imports for heavy components:** ```typescript import dynamic from 'next/dynamic'; const HeavyChart = dynamic(() => import('./HeavyChart'), { loading: () => , ssr: false, }); ``` ### 12.2 Memoization **Use React.memo for expensive renders:** ```typescript export const UserCard = React.memo(function UserCard({ user }: UserCardProps) { return
{user.name}
; }); ``` **Use useMemo for expensive calculations:** ```typescript const sortedUsers = useMemo(() => { return users.sort((a, b) => a.name.localeCompare(b.name)); }, [users]); ``` ### 12.3 Image Optimization **Always use Next.js Image component:** ```typescript import Image from 'next/image'; User avatar ``` --- ## 13. Security Best Practices ### 13.1 Input Sanitization **Never render raw HTML:** ```typescript // ✅ Good: React escapes by default
{userInput}
// ❌ Bad: XSS vulnerability
``` ### 13.2 Environment Variables **Never commit secrets:** ```typescript // ✅ Good: Use env variables const apiUrl = process.env.NEXT_PUBLIC_API_URL; // ❌ Bad: Hardcoded const apiUrl = 'https://api.example.com'; ``` **Public vs Private:** - `NEXT_PUBLIC_*`: Exposed to browser - Other vars: Server-side only --- ## 14. Code Review Checklist **Before submitting PR:** - [ ] All tests pass - [ ] No TypeScript errors - [ ] ESLint passes - [ ] Code follows naming conventions - [ ] Components are typed - [ ] Accessibility considerations met - [ ] Error handling implemented - [ ] Loading states implemented - [ ] No console.log statements - [ ] No commented-out code - [ ] Documentation updated if needed --- ## Conclusion These standards ensure consistency, maintainability, and quality across the codebase. Follow them rigorously, and update this document as the project evolves. For specific patterns and examples, refer to: - **ARCHITECTURE.md**: System design and patterns - **COMPONENT_GUIDE.md**: Component usage and examples - **FEATURE_EXAMPLES.md**: Step-by-step implementation guides