📚 Mundarija#
- React Architecture: Lead Developer Perspective
- Project Structure va Monorepo
- State Management Strategy
- Component Design Patterns
- Performance Optimization
- Testing Strategy
- CI/CD va Deployment
- Code Quality va Developer Experience
- Security va Error Handling
- Team Leadership va Best Practices
🏗️ React Architecture: Lead Developer Perspective#
Senior Team Lead sifatida siz nafaqat kod yozasiz, balki architectural decisions qabul qilasiz, team best practices ni belgilaysiz va scalability ni ta'minlaysiz.
Asosiy Prinsiplar#
| Prinsip | Tavsif |
|---|---|
| Separation of Concerns | UI, biznes logika, data layer alohida |
| Single Responsibility | Har bir komponent/function faqat bir ishni bajaradi |
| DRY (Don't Repeat Yourself) | Kodni qayta ishlatish |
| Composition over Inheritance | Komponentlarni birlashtirish orqali yaratish |
| Loose Coupling | Modullar bir-biriga kam bog'liq |
| High Cohesion | Bog'liq funksiyalar bir joyda |
📁 Project Structure va Monorepo#
1. Monorepo Architecture (Nx/Turborepo)#
my-monorepo/
├── apps/
│ ├── web/ # Main web application
│ │ ├── app/ # Next.js App Router
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── utils/
│ │ └── public/
│ ├── admin/ # Admin dashboard
│ ├── mobile/ # React Native app
│ └── docs/ # Documentation site
├── packages/
│ ├── ui/ # Shared UI components
│ │ ├── src/
│ │ │ ├── atoms/
│ │ │ ├── molecules/
│ │ │ ├── organisms/
│ │ │ └── templates/
│ │ ├── index.ts
│ │ └── package.json
│ ├── api/ # API client
│ │ ├── src/
│ │ │ ├── client.ts
│ │ │ ├── hooks/
│ │ │ └── types.ts
│ │ └── package.json
│ ├── config/ # Shared configs
│ │ ├── eslint/
│ │ ├── typescript/
│ │ └── prettier/
│ ├── utils/ # Shared utilities
│ ├── hooks/ # Shared React hooks
│ └── types/ # Shared TypeScript types
├── tools/
│ ├── generators/ # Code generators
│ └── scripts/ # Build scripts
├── .github/
│ └── workflows/
├── nx.json
├── turbo.json
├── package.json
├── tsconfig.base.json
└── README.md
2. Application Structure (Feature-based)#
apps/web/src/
├── app/ # Next.js App Router
│ ├── (auth)/
│ │ ├── login/
│ │ │ └── page.tsx
│ │ └── register/
│ │ └── page.tsx
│ ├── (dashboard)/
│ │ ├── dashboard/
│ │ │ └── page.tsx
│ │ └── settings/
│ │ └── page.tsx
│ ├── api/ # API routes
│ └── layout.tsx
├── features/ # Feature modules
│ ├── auth/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── services/
│ │ ├── types/
│ │ ├── utils/
│ │ └── index.ts
│ ├── dashboard/
│ ├── users/
│ ├── products/
│ └── settings/
├── lib/ # Core libraries
│ ├── api/
│ ├── store/
│ ├── hooks/
│ ├── utils/
│ └── constants/
├── shared/ # Shared resources
│ ├── components/
│ ├── hooks/
│ ├── types/
│ └── utils/
├── styles/
├── types/
└── config/
🎯 State Management Strategy#
Layered State Management#
| Layer | Management | Purpose |
|---|---|---|
| Server State | TanStack Query | API ma'lumotlari, caching, pagination |
| Global State | Zustand/Jotai | User, theme, language, notifications |
| Form State | React Hook Form | Form validation, submission |
| URL State | Next.js Router | Query params, routing |
| Local State | useState/useReducer | Component-specific state |
1. Server State (TanStack Query)#
// lib/api/query-client.ts
import { QueryClient, QueryClientConfig } from '@tanstack/react-query';
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister';
import { persistQueryClient } from '@tanstack/react-query-persist-client';
// Query client configuration
export const queryClientConfig: QueryClientConfig = {
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes
gcTime: 1000 * 60 * 30, // 30 minutes
retry: 3,
retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000),
refetchOnWindowFocus: false,
refetchOnMount: false,
refetchOnReconnect: false,
networkMode: 'offlineFirst',
throwOnError: false,
},
mutations: {
retry: 1,
networkMode: 'offlineFirst',
},
},
};
export const queryClient = new QueryClient(queryClientConfig);
// Persistence for offline support (browser only)
if (typeof window !== 'undefined') {
const persister = createSyncStoragePersister({
storage: window.localStorage,
key: 'REACT_QUERY_OFFLINE_CACHE',
throttleTime: 1000,
});
persistQueryClient({
queryClient,
persister,
maxAge: 1000 * 60 * 60 * 24, // 24 hours
});
}
// features/users/services/user.service.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiClient } from '@/lib/api/client';
import { User, CreateUserDto, UpdateUserDto } from '../types';
const USER_QUERY_KEY = 'users';
// Hooks for data fetching
export const userQueries = {
// Get all users with pagination
useUsers: (params: { page: number; limit: number }) => {
return useQuery({
queryKey: [USER_QUERY_KEY, 'list', params],
queryFn: async () => {
const response = await apiClient.get<{ data: User[]; pagination: any }>(
'/users',
{ params }
);
return response.data;
},
placeholderData: (previousData) => previousData,
});
},
// Get single user
useUser: (id: string) => {
return useQuery({
queryKey: [USER_QUERY_KEY, 'detail', id],
queryFn: async () => {
const response = await apiClient.get<User>(`/users/${id}`);
return response.data;
},
enabled: !!id,
});
},
// Search users
useSearchUsers: (searchTerm: string) => {
return useQuery({
queryKey: [USER_QUERY_KEY, 'search', searchTerm],
queryFn: async () => {
const response = await apiClient.get<User[]>('/users/search', {
params: { q: searchTerm },
});
return response.data;
},
enabled: searchTerm.length > 2,
});
},
};
// Mutations
export const userMutations = {
useCreateUser: () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (data: CreateUserDto) => {
const response = await apiClient.post<User>('/users', data);
return response.data;
},
onSuccess: (newUser) => {
// Update cache
queryClient.setQueryData<User[]>([USER_QUERY_KEY, 'list'], (old) => {
return old ? [...old, newUser] : [newUser];
});
queryClient.invalidateQueries({
queryKey: [USER_QUERY_KEY, 'list'],
});
},
onError: (error) => {
console.error('Failed to create user:', error);
},
});
},
useUpdateUser: () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ id, data }: { id: string; data: UpdateUserDto }) => {
const response = await apiClient.patch<User>(`/users/${id}`, data);
return response.data;
},
onSuccess: (updatedUser) => {
// Update cache
queryClient.setQueryData<User>([USER_QUERY_KEY, 'detail', updatedUser.id], updatedUser);
queryClient.invalidateQueries({
queryKey: [USER_QUERY_KEY],
});
},
});
},
useDeleteUser: () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (id: string) => {
await apiClient.delete(`/users/${id}`);
return id;
},
onSuccess: (deletedId) => {
queryClient.invalidateQueries({
queryKey: [USER_QUERY_KEY],
});
queryClient.setQueryData<User[]>([USER_QUERY_KEY, 'list'], (old) => {
return old ? old.filter((user) => user.id !== deletedId) : [];
});
},
});
},
};
2. Global State (Zustand)#
// lib/store/auth.store.ts
import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { User, AuthState } from '@/features/auth/types';
import { authService } from '@/features/auth/services/auth.service';
interface AuthStore extends AuthState {
// Actions
login: (email: string, password: string) => Promise<void>;
logout: () => Promise<void>;
register: (data: any) => Promise<void>;
setUser: (user: User | null) => void;
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
reset: () => void;
}
const initialState: AuthState = {
user: null,
token: null,
isAuthenticated: false,
isLoading: false,
error: null,
};
export const useAuthStore = create<AuthStore>()(
devtools(
immer((set, get) => ({
...initialState,
login: async (email: string, password: string) => {
set({ isLoading: true, error: null });
try {
const { user, token } = await authService.login({ email, password });
set({
user,
token,
isAuthenticated: true,
isLoading: false,
});
} catch (error: any) {
set({
error: error.message || 'Login failed',
isLoading: false,
});
throw error;
}
},
logout: async () => {
set({ isLoading: true });
try {
await authService.logout();
set(initialState);
} catch (error: any) {
set({
error: error.message || 'Logout failed',
isLoading: false,
});
throw error;
}
},
register: async (data: any) => {
set({ isLoading: true, error: null });
try {
const { user, token } = await authService.register(data);
set({
user,
token,
isAuthenticated: true,
isLoading: false,
});
} catch (error: any) {
set({
error: error.message || 'Registration failed',
isLoading: false,
});
throw error;
}
},
setUser: (user) => {
set({ user, isAuthenticated: !!user });
},
setLoading: (isLoading) => {
set({ isLoading });
},
setError: (error) => {
set({ error });
},
reset: () => {
set(initialState);
},
})),
{
name: 'auth-store',
}
)
);
3. Form State (React Hook Form + Zod)#
// features/auth/components/LoginForm.tsx
'use client';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { useAuthStore } from '@/lib/store/auth.store';
import { Button, Input, Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@/shared/components/ui';
// Schema
const loginSchema = z.object({
email: z.string().email('Invalid email address'),
password: z.string().min(6, 'Password must be at least 6 characters'),
rememberMe: z.boolean().optional(),
});
type LoginFormData = z.infer<typeof loginSchema>;
export function LoginForm() {
const { login, isLoading, error } = useAuthStore();
const form = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
defaultValues: {
email: '',
password: '',
rememberMe: false,
},
mode: 'onBlur',
});
const onSubmit = async (data: LoginFormData) => {
try {
await login(data.email, data.password);
// Redirect handled by middleware
} catch (error) {
// Error is already set in store
}
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input
type="email"
placeholder="user@example.com"
{...field}
disabled={isLoading}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({ field }) => (
<FormItem>
<FormLabel>Password</FormLabel>
<FormControl>
<Input
type="password"
placeholder="********"
{...field}
disabled={isLoading}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="rememberMe"
render={({ field }) => (
<FormItem className="flex items-center gap-2">
<FormControl>
<input
type="checkbox"
checked={field.value}
onChange={field.onChange}
disabled={isLoading}
/>
</FormControl>
<FormLabel>Remember me</FormLabel>
</FormItem>
)}
/>
{error && (
<div className="text-sm text-red-500 bg-red-50 p-3 rounded-md">
{error}
</div>
)}
<Button
type="submit"
className="w-full"
disabled={isLoading || !form.formState.isValid}
>
{isLoading ? (
<span className="flex items-center gap-2">
<span className="animate-spin">⏳</span>
Signing in...
</span>
) : (
'Sign in'
)}
</Button>
</form>
</Form>
);
}
🧩 Component Design Patterns#
1. Atomic Design Structure#
// packages/ui/src/atoms/Button/Button.tsx
import { forwardRef, ButtonHTMLAttributes, ReactNode } from 'react';
import { cn } from '@repo/utils/cn';
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
leftIcon?: ReactNode;
rightIcon?: ReactNode;
fullWidth?: boolean;
children?: ReactNode;
}
const variantStyles = {
primary: 'bg-blue-600 text-white hover:bg-blue-700 active:bg-blue-800',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300 active:bg-gray-400',
outline: 'border-2 border-blue-600 text-blue-600 hover:bg-blue-50',
ghost: 'text-gray-700 hover:bg-gray-100 hover:text-gray-900',
danger: 'bg-red-600 text-white hover:bg-red-700 active:bg-red-800',
};
const sizeStyles = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
};
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(
{
variant = 'primary',
size = 'md',
isLoading = false,
leftIcon,
rightIcon,
fullWidth = false,
className,
children,
disabled,
...props
},
ref
) => {
return (
<button
ref={ref}
className={cn(
'inline-flex items-center justify-center rounded-lg font-medium transition-all duration-200',
'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2',
'disabled:opacity-50 disabled:cursor-not-allowed',
variantStyles[variant],
sizeStyles[size],
fullWidth && 'w-full',
className
)}
disabled={disabled || isLoading}
{...props}
>
{isLoading && (
<span className="mr-2 animate-spin">
<LoadingSpinner size="sm" />
</span>
)}
{leftIcon && <span className="mr-2">{leftIcon}</span>}
{children}
{rightIcon && <span className="ml-2">{rightIcon}</span>}
</button>
);
}
);
Button.displayName = 'Button';
2. Compound Components Pattern#
// packages/ui/src/molecules/Select/Select.tsx
'use client';
import React, { createContext, useContext, useState, ReactNode } from 'react';
import { cn } from '@repo/utils/cn';
interface SelectContextType {
value: string;
onChange: (value: string) => void;
isOpen: boolean;
setIsOpen: (isOpen: boolean) => void;
}
const SelectContext = createContext<SelectContextType | undefined>(undefined);
const useSelect = () => {
const context = useContext(SelectContext);
if (!context) {
throw new Error('Select components must be used within Select');
}
return context;
};
interface SelectProps {
value: string;
onChange: (value: string) => void;
children: ReactNode;
className?: string;
placeholder?: string;
}
const SelectRoot = ({
value,
onChange,
children,
className,
placeholder = 'Select...',
}: SelectProps) => {
const [isOpen, setIsOpen] = useState(false);
return (
<SelectContext.Provider value={{ value, onChange, isOpen, setIsOpen }}>
<div className={cn('relative', className)}>
<div
className="flex cursor-pointer items-center justify-between rounded-lg border border-gray-300 px-4 py-2"
onClick={() => setIsOpen(!isOpen)}
>
<span className={cn(!value && 'text-gray-400')}>
{value || placeholder}
</span>
<span className={cn('transition-transform', isOpen && 'rotate-180')}>
▼
</span>
</div>
{isOpen && (
<div className="absolute z-10 mt-1 w-full rounded-lg border border-gray-200 bg-white shadow-lg">
{children}
</div>
)}
</div>
</SelectContext.Provider>
);
};
const SelectOption = ({
value,
children,
className,
}: {
value: string;
children: ReactNode;
className?: string;
}) => {
const { value: selectedValue, onChange, setIsOpen } = useSelect();
const handleClick = () => {
onChange(value);
setIsOpen(false);
};
const isSelected = selectedValue === value;
return (
<div
className={cn(
'cursor-pointer px-4 py-2 hover:bg-gray-100',
isSelected && 'bg-blue-50 text-blue-600',
className
)}
onClick={handleClick}
>
{children}
</div>
);
};
export const Select = Object.assign(SelectRoot, {
Option: SelectOption,
});
3. Render Props Pattern#
// shared/components/DataFetcher/DataFetcher.tsx
import { ReactNode, useEffect, useState } from 'react';
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
interface DataFetcherProps<TData, TError> {
queryKey: string[];
queryFn: () => Promise<TData>;
options?: Omit<UseQueryOptions<TData, TError>, 'queryKey' | 'queryFn'>;
children: (props: {
data: TData | undefined;
isLoading: boolean;
isError: boolean;
error: TError | null;
refetch: () => void;
}) => ReactNode;
loadingComponent?: ReactNode;
errorComponent?: (error: TError, refetch: () => void) => ReactNode;
}
export function DataFetcher<TData = any, TError = Error>({
queryKey,
queryFn,
options,
children,
loadingComponent,
errorComponent,
}: DataFetcherProps<TData, TError>) {
const { data, isLoading, isError, error, refetch } = useQuery({
queryKey,
queryFn,
...options,
});
if (isLoading) {
return loadingComponent || <DefaultLoading />;
}
if (isError) {
return errorComponent ? (
errorComponent(error as TError, refetch)
) : (
<DefaultError error={error as TError} refetch={refetch} />
);
}
return children({ data, isLoading, isError, error: null, refetch });
}
const DefaultLoading = () => (
<div className="flex items-center justify-center p-8">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-blue-500 border-t-transparent" />
</div>
);
const DefaultError = ({ error, refetch }: any) => (
<div className="rounded-lg border border-red-200 bg-red-50 p-4 text-red-700">
<p className="font-medium">Error loading data</p>
<p className="text-sm">{error?.message}</p>
<button
onClick={() => refetch()}
className="mt-2 rounded bg-red-100 px-4 py-2 text-sm text-red-700 hover:bg-red-200"
>
Try Again
</button>
</div>
);
4. Container/Presentational Pattern#
// features/users/components/UserList.container.tsx
'use client';
import { useUsers } from '../services/user.service';
import { UserListPresenter } from './UserList.presenter';
import { useState } from 'react';
export function UserListContainer() {
const [page, setPage] = useState(1);
const { data, isLoading, isError, error } = useUsers({ page, limit: 10 });
const handlePageChange = (newPage: number) => {
setPage(newPage);
};
const handleRefresh = () => {
// Refresh logic
};
return (
<UserListPresenter
users={data?.data || []}
pagination={data?.pagination}
isLoading={isLoading}
isError={isError}
error={error}
onPageChange={handlePageChange}
onRefresh={handleRefresh}
/>
);
}
// features/users/components/UserList.presenter.tsx
import { User } from '../types';
import { UserCard } from './UserCard';
import { Pagination } from '@/shared/components/ui';
import { LoadingSpinner } from '@/shared/components/ui';
interface UserListPresenterProps {
users: User[];
pagination?: {
page: number;
totalPages: number;
total: number;
};
isLoading: boolean;
isError: boolean;
error: Error | null;
onPageChange: (page: number) => void;
onRefresh: () => void;
}
export function UserListPresenter({
users,
pagination,
isLoading,
isError,
error,
onPageChange,
onRefresh,
}: UserListPresenterProps) {
if (isLoading) {
return (
<div className="flex min-h-[200px] items-center justify-center">
<LoadingSpinner size="lg" />
</div>
);
}
if (isError) {
return (
<div className="rounded-lg border border-red-200 bg-red-50 p-8 text-center">
<h3 className="text-lg font-medium text-red-700">Failed to load users</h3>
<p className="mt-2 text-sm text-red-600">{error?.message}</p>
<button
onClick={onRefresh}
className="mt-4 rounded bg-red-100 px-4 py-2 text-sm text-red-700 hover:bg-red-200"
>
Retry
</button>
</div>
);
}
if (users.length === 0) {
return (
<div className="text-center py-12">
<p className="text-gray-500">No users found</p>
</div>
);
}
return (
<div className="space-y-6">
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
{users.map((user) => (
<UserCard key={user.id} user={user} />
))}
</div>
{pagination && pagination.totalPages > 1 && (
<div className="mt-6 flex justify-center">
<Pagination
currentPage={pagination.page}
totalPages={pagination.totalPages}
onPageChange={onPageChange}
/>
</div>
)}
</div>
);
}
⚡ Performance Optimization#
1. Code Splitting#
// app/layout.tsx
'use client';
import { Suspense, lazy } from 'react';
import { LoadingSpinner } from '@/shared/components/ui';
// Lazy load heavy components
const HeavyComponent = lazy(() => import('@/features/heavy/HeavyComponent'));
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
{children}
<Suspense fallback={<LoadingSpinner size="lg" />}>
<HeavyComponent />
</Suspense>
</body>
</html>
);
}
2. Memoization Strategy#
// shared/hooks/useMemoizedValue.ts
import { useMemo, useRef, useEffect } from 'react';
import { usePrevious } from './usePrevious';
export function useMemoizedValue<T>(
value: T,
compareFn: (prev: T, next: T) => boolean = Object.is
): T {
const previousRef = useRef<T>(value);
if (!compareFn(previousRef.current, value)) {
previousRef.current = value;
}
return previousRef.current;
}
// Component optimization
import { memo, useCallback, useMemo } from 'react';
export const OptimizedUserCard = memo(({ user, onUpdate }: UserCardProps) => {
// Memoize expensive computations
const fullName = useMemo(() => {
return `${user.firstName} ${user.lastName}`.trim();
}, [user.firstName, user.lastName]);
// Memoize callbacks
const handleUpdate = useCallback(() => {
onUpdate(user.id);
}, [onUpdate, user.id]);
// Memoize complex objects
const userPermissions = useMemo(() => {
return user.permissions.filter(p => p.isActive);
}, [user.permissions]);
return (
<div>
<h3>{fullName}</h3>
<button onClick={handleUpdate}>Update</button>
</div>
);
});
OptimizedUserCard.displayName = 'OptimizedUserCard';
3. Virtualization (React Window)#
// shared/components/VirtualList/VirtualList.tsx
'use client';
import { useRef, useEffect } from 'react';
import { FixedSizeList, VariableSizeList } from 'react-window';
import { useWindowWidth } from '@/shared/hooks/useWindowWidth';
interface VirtualListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
itemSize?: number;
height?: number;
width?: number | string;
isVariableSize?: boolean;
getItemSize?: (index: number) => number;
}
export function VirtualList<T>({
items,
renderItem,
itemSize = 50,
height = 500,
width = '100%',
isVariableSize = false,
getItemSize,
}: VirtualListProps<T>) {
const windowWidth = useWindowWidth();
const listRef = useRef<FixedSizeList | VariableSizeList>(null);
useEffect(() => {
// Reset scroll position when items change
listRef.current?.scrollTo(0);
}, [items]);
const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => {
const item = items[index];
if (!item) return null;
return <div style={style}>{renderItem(item, index)}</div>;
};
const ListComponent = isVariableSize ? VariableSizeList : FixedSizeList;
return (
<ListComponent
ref={listRef as any}
height={height}
width={width}
itemCount={items.length}
itemSize={isVariableSize ? getItemSize : itemSize}
overscanCount={10}
>
{Row}
</ListComponent>
);
}
4. Image Optimization#
// shared/components/OptimizedImage/OptimizedImage.tsx
'use client';
import { useState, useEffect } from 'react';
import Image from 'next/image';
import { cn } from '@repo/utils/cn';
interface OptimizedImageProps {
src: string;
alt: string;
width?: number;
height?: number;
sizes?: string;
className?: string;
quality?: number;
priority?: boolean;
placeholder?: 'blur' | 'empty';
blurDataURL?: string;
}
export function OptimizedImage({
src,
alt,
width,
height,
sizes = '(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw',
className,
quality = 85,
priority = false,
placeholder = 'empty',
blurDataURL,
}: OptimizedImageProps) {
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(false);
// Intersection Observer for lazy loading
useEffect(() => {
if (priority) setIsLoading(false);
}, [priority]);
return (
<div className={cn('relative overflow-hidden', className)}>
<Image
src={src}
alt={alt}
width={width}
height={height}
sizes={sizes}
quality={quality}
priority={priority}
placeholder={placeholder}
blurDataURL={blurDataURL}
className={cn(
'transition-opacity duration-300',
isLoading ? 'opacity-0 scale-105' : 'opacity-100 scale-100'
)}
onLoadingComplete={() => setIsLoading(false)}
onError={() => {
setError(true);
setIsLoading(false);
}}
/>
{isLoading && (
<div className="absolute inset-0 bg-gray-200 animate-pulse" />
)}
{error && (
<div className="absolute inset-0 flex items-center justify-center bg-gray-100 text-gray-500">
<span>Failed to load image</span>
</div>
)}
</div>
);
}
🧪 Testing Strategy#
1. Unit Testing (Jest + React Testing Library)#
// features/auth/components/LoginForm.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from './LoginForm';
import { useAuthStore } from '@/lib/store/auth.store';
// Mock the store
jest.mock('@/lib/store/auth.store');
describe('LoginForm', () => {
const mockLogin = jest.fn();
beforeEach(() => {
(useAuthStore as jest.Mock).mockImplementation(() => ({
login: mockLogin,
isLoading: false,
error: null,
}));
});
it('should render all form fields', () => {
render(<LoginForm />);
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /sign in/i })).toBeInTheDocument();
});
it('should validate email field', async () => {
render(<LoginForm />);
const emailInput = screen.getByLabelText(/email/i);
const submitButton = screen.getByRole('button', { name: /sign in/i });
// Test invalid email
await userEvent.type(emailInput, 'invalid-email');
fireEvent.blur(emailInput);
await waitFor(() => {
expect(screen.getByText(/invalid email address/i)).toBeInTheDocument();
});
// Test valid email
await userEvent.clear(emailInput);
await userEvent.type(emailInput, 'test@example.com');
fireEvent.blur(emailInput);
await waitFor(() => {
expect(screen.queryByText(/invalid email address/i)).not.toBeInTheDocument();
});
});
it('should submit form with valid data', async () => {
render(<LoginForm />);
await userEvent.type(screen.getByLabelText(/email/i), 'test@example.com');
await userEvent.type(screen.getByLabelText(/password/i), 'password123');
fireEvent.click(screen.getByRole('button', { name: /sign in/i }));
await waitFor(() => {
expect(mockLogin).toHaveBeenCalledWith('test@example.com', 'password123');
});
});
it('should show loading state while submitting', async () => {
(useAuthStore as jest.Mock).mockImplementation(() => ({
login: jest.fn(),
isLoading: true,
error: null,
}));
render(<LoginForm />);
expect(screen.getByText(/signing in/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /signing in/i })).toBeDisabled();
});
it('should display error message on login failure', async () => {
const errorMessage = 'Invalid credentials';
(useAuthStore as jest.Mock).mockImplementation(() => ({
login: jest.fn().mockRejectedValue(new Error(errorMessage)),
isLoading: false,
error: errorMessage,
}));
render(<LoginForm />);
await waitFor(() => {
expect(screen.getByText(errorMessage)).toBeInTheDocument();
});
});
});
2. Integration Testing#
// __tests__/integration/auth-flow.test.tsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { AuthProvider } from '@/context/AuthContext';
import { LoginForm } from '@/features/auth/components/LoginForm';
// Mock API client
jest.mock('@/lib/api/client');
describe('Authentication Flow Integration', () => {
it('should complete login flow successfully', async () => {
// Mock successful login response
const mockLogin = jest.fn().mockResolvedValue({
user: { id: '1', email: 'test@example.com' },
token: 'mock-token',
});
// Setup test
const user = userEvent.setup();
render(
<AuthProvider>
<LoginForm />
</AuthProvider>
);
// Fill form
await user.type(screen.getByLabelText(/email/i), 'test@example.com');
await user.type(screen.getByLabelText(/password/i), 'password123');
// Submit
await user.click(screen.getByRole('button', { name: /sign in/i }));
// Verify navigation or success message
await waitFor(() => {
expect(screen.queryByRole('button', { name: /sign in/i })).not.toBeInTheDocument();
});
});
});
3. E2E Testing (Cypress/Playwright)#
// e2e/auth.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Authentication Flow', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test('should login successfully', async ({ page }) => {
// Fill form
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="password"]', 'password123');
// Submit
await page.click('button[type="submit"]');
// Wait for navigation
await page.waitForURL('/dashboard');
// Verify user is logged in
await expect(page.locator('[data-testid="user-menu"]')).toBeVisible();
await expect(page.locator('[data-testid="user-email"]')).toContainText('test@example.com');
});
test('should show error for invalid credentials', async ({ page }) => {
await page.fill('[name="email"]', 'wrong@example.com');
await page.fill('[name="password"]', 'wrongpassword');
await page.click('button[type="submit"]');
await expect(page.locator('[role="alert"]')).toBeVisible();
await expect(page.locator('[role="alert"]')).toContainText('Invalid credentials');
});
test('should logout successfully', async ({ page }) => {
// Login first
await page.fill('[name="email"]', 'test@example.com');
await page.fill('[name="password"]', 'password123');
await page.click('button[type="submit"]');
await page.waitForURL('/dashboard');
// Logout
await page.click('[data-testid="user-menu"]');
await page.click('[data-testid="logout-button"]');
await expect(page).toHaveURL('/login');
});
});
🚀 CI/CD va Deployment#
GitHub Actions Workflow#
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: '20'
jobs:
# Quality checks
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Type check
run: yarn type-check
- name: Lint
run: yarn lint
- name: Format check
run: yarn format:check
# Tests
test:
runs-on: ubuntu-latest
needs: quality
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run unit tests
run: yarn test:unit --coverage
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/lcov.info
- name: Run integration tests
run: yarn test:integration
# Build
build:
runs-on: ubuntu-latest
needs: [quality, test]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Build application
run: yarn build
- name: Upload build artifacts
uses: actions/upload-artifact@v3
with:
name: build
path: |
.next
public
package.json
# Deploy to Vercel
deploy:
runs-on: ubuntu-latest
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Download build artifacts
uses: actions/download-artifact@v3
with:
name: build
- name: Deploy to Vercel
uses: amondnet/vercel-action@v20
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.ORG_ID }}
vercel-project-id: ${{ secrets.PROJECT_ID }}
vercel-args: '--prod'
# E2E Tests on Staging
e2e:
runs-on: ubuntu-latest
needs: deploy
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Install Playwright
run: npx playwright install --with-deps
- name: Run E2E tests
run: yarn test:e2e
env:
BASE_URL: ${{ secrets.STAGING_URL }}
Vercel Configuration#
{
"name": "my-app",
"version": 2,
"builds": [
{
"src": "apps/web/package.json",
"use": "@vercel/next"
}
],
"env": {
"NEXT_PUBLIC_API_URL": "https://api.example.com",
"NEXT_PUBLIC_APP_URL": "https://myapp.com"
},
"regions": ["iad1", "sin1"],
"functions": {
"apps/web/next.config.js": {
"maxDuration": 30
}
},
"rewrites": [
{
"source": "/(.*)",
"destination": "/apps/web/$1"
}
]
}
🔒 Security va Error Handling#
1. Security Best Practices#
// lib/security/helmet.ts
import { Helmet } from 'react-helmet';
export function SecurityHeaders() {
return (
<Helmet>
<meta httpEquiv="X-Content-Type-Options" content="nosniff" />
<meta httpEquiv="X-Frame-Options" content="DENY" />
<meta httpEquiv="X-XSS-Protection" content="1; mode=block" />
<meta name="referrer" content="strict-origin-when-cross-origin" />
{/* Content Security Policy */}
<meta
httpEquiv="Content-Security-Policy"
content={`
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval' https:;
style-src 'self' 'unsafe-inline' https:;
img-src 'self' data: https: blob:;
font-src 'self' data: https:;
connect-src 'self' https://api.example.com wss:;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
`.replace(/\s+/g, ' ').trim()}
/>
</Helmet>
);
}
2. Error Boundary#
// shared/components/ErrorBoundary/ErrorBoundary.tsx
'use client';
import React from 'react';
import { logger } from '@/lib/logger';
interface Props {
children: React.ReactNode;
fallback?: React.ReactNode;
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
}
interface State {
hasError: boolean;
error: Error | null;
}
export class ErrorBoundary extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
// Log error to service
logger.error('React Error Boundary caught error:', error, errorInfo);
// Call custom error handler
this.props.onError?.(error, errorInfo);
// Send to error reporting service
if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
// Sentry.captureException(error, { extra: errorInfo });
}
}
handleReset = () => {
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
}
return (
<div className="flex min-h-[400px] flex-col items-center justify-center p-8 text-center">
<div className="rounded-lg bg-red-50 p-8 max-w-md w-full">
<h2 className="text-2xl font-bold text-red-700 mb-4">
Something went wrong
</h2>
<p className="text-red-600 mb-4">
{this.state.error?.message || 'An unexpected error occurred'}
</p>
<div className="flex gap-4 justify-center">
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-red-100 text-red-700 rounded hover:bg-red-200"
>
Reload page
</button>
<button
onClick={this.handleReset}
className="px-4 py-2 bg-blue-100 text-blue-700 rounded hover:bg-blue-200"
>
Try again
</button>
</div>
{process.env.NODE_ENV === 'development' && (
<pre className="mt-4 text-left text-xs bg-red-100 p-4 rounded overflow-auto">
{this.state.error?.stack}
</pre>
)}
</div>
</div>
);
}
return this.props.children;
}
}
👥 Team Leadership va Best Practices#
1. Development Standards#
// .eslintrc.js
module.exports = {
extends: [
'next/core-web-vitals',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'plugin:jest/recommended',
'plugin:testing-library/react',
'plugin:import/typescript',
'prettier'
],
plugins: [
'@typescript-eslint',
'react',
'react-hooks',
'jest',
'testing-library',
'import',
'unused-imports'
],
rules: {
// TypeScript
'@typescript-eslint/explicit-function-return-type': 'warn',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/ban-ts-comment': 'warn',
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}],
// React
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'react/display-name': 'warn',
'react/jsx-key': 'error',
'react/jsx-no-duplicate-props': 'error',
'react/jsx-no-target-blank': 'error',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
// Import
'import/order': ['error', {
groups: [
'builtin',
'external',
'internal',
'parent',
'sibling',
'index',
'object',
'type'
],
pathGroups: [
{
pattern: 'react',
group: 'external',
position: 'before'
},
{
pattern: '@/**',
group: 'internal'
}
],
pathGroupsExcludedImportTypes: ['react'],
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true
}
}],
// Unused imports
'unused-imports/no-unused-imports': 'error',
'unused-imports/no-unused-vars': ['warn', {
vars: 'all',
varsIgnorePattern: '^_',
args: 'after-used',
argsIgnorePattern: '^_'
}]
},
settings: {
'import/resolver': {
typescript: {
project: './tsconfig.json'
}
}
}
};
2. Git Workflow#
# .github/PULL_REQUEST_TEMPLATE.md
## Description
<!-- Describe your changes in detail -->
## Type of Change
- [ ] 🐛 Bug fix
- [ ] ✨ New feature
- [ ] 🔧 Refactor
- [ ] 📝 Documentation update
- [ ] 🎨 UI/UX improvement
- [ ] ⚡ Performance improvement
- [ ] ✅ Test coverage
## How Has This Been Tested?
- [ ] Unit tests
- [ ] Integration tests
- [ ] E2E tests
- [ ] Manual testing
## Checklist
- [ ] Code follows project style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] Tests added/updated
- [ ] No new warnings
- [ ] All checks pass
## Screenshots (if applicable)
<!-- Add screenshots here -->
## Additional Notes
<!-- Any additional information -->
3. Code Review Checklist#
## Code Review Checklist
### Architecture & Design
- [ ] Follows project architecture principles
- [ ] Proper separation of concerns
- [ ] Scalable and maintainable
- [ ] Performance considerations
- [ ] Security best practices
### Code Quality
- [ ] Clean, readable, and self-documenting
- [ ] No duplication (DRY)
- [ ] Proper error handling
- [ ] Adequate logging
- [ ] Type safety
### Testing
- [ ] Unit tests for business logic
- [ ] Integration tests for critical paths
- [ ] E2E tests for user flows
- [ ] Edge cases covered
- [ ] Test coverage meets threshold
### Performance
- [ ] Bundle size impact
- [ ] Memoization where needed
- [ ] Proper lazy loading
- [ ] No unnecessary re-renders
- [ ] API calls optimized
### Accessibility
- [ ] Proper ARIA attributes
- [ ] Keyboard navigation
- [ ] Screen reader compatibility
- [ ] Color contrast
- [ ] Focus management
### Documentation
- [ ] JSDoc/TSDoc comments
- [ ] README updated
- [ ] Storybook/UI docs
- [ ] API documentation
- [ ] Migration guide if needed
4. Daily Standup Template#
## Daily Standup Meeting
### Yesterday
- [ ] Task 1: [Status] [Link]
- [ ] Task 2: [Status] [Link]
- [ ] Task 3: [Status] [Link]
### Today
- [ ] Task 1: [Priority] [Link]
- [ ] Task 2: [Priority] [Link]
- [ ] Task 3: [Priority] [Link]
### Blockers
- [ ] Issue 1: [Description] [Assigned to]
- [ ] Issue 2: [Description] [Assigned to]
### Notes
- Team announcements
- Dependencies
- Decisions made
- Questions for the team
### Metrics
- Velocity: [Current] / [Target]
- Code coverage: [%]
- Open PRs: [Count]
- Bugs: [Open] / [Closed]
📊 Monitoring va Analytics#
Application Monitoring#
// lib/monitoring/performance.ts
import { logger } from '@/lib/logger';
interface PerformanceMetric {
name: string;
value: number;
tags?: Record<string, string>;
}
class PerformanceMonitor {
private metrics: PerformanceMetric[] = [];
measure<T>(name: string, fn: () => T | Promise<T>): T | Promise<T> {
const start = performance.now();
try {
const result = fn();
if (result instanceof Promise) {
return result.finally(() => {
const duration = performance.now() - start;
this.recordMetric(name, duration);
}) as Promise<T>;
}
const duration = performance.now() - start;
this.recordMetric(name, duration);
return result;
} catch (error) {
throw error;
}
}
recordMetric(name: string, value: number, tags?: Record<string, string>) {
this.metrics.push({ name, value, tags });
// Send to monitoring service
if (process.env.NODE_ENV === 'production') {
// Send to DataDog/NewRelic/Sentry
// datadog.timing(name, value, tags);
}
if (value > 1000) {
logger.warn(`Performance warning: ${name} took ${value}ms`);
}
}
getMetrics() {
return this.metrics;
}
clear() {
this.metrics = [];
}
}
export const performanceMonitor = new PerformanceMonitor();
// Usage
export function usePerformanceMonitoring(componentName: string) {
useEffect(() => {
performanceMonitor.recordMetric(`component.mount.${componentName}`, performance.now());
return () => {
performanceMonitor.recordMetric(`component.unmount.${componentName}`, performance.now());
};
}, [componentName]);
}
Analytics Integration#
// lib/analytics/analytics.ts
import { logger } from '@/lib/logger';
interface AnalyticsEvent {
name: string;
properties?: Record<string, any>;
userId?: string;
timestamp?: Date;
}
class AnalyticsService {
private enabled = process.env.NODE_ENV === 'production';
private userId: string | null = null;
setUserId(userId: string) {
this.userId = userId;
}
trackEvent(event: AnalyticsEvent) {
if (!this.enabled) {
logger.debug('Analytics event (dev):', event);
return;
}
try {
const payload = {
...event,
userId: event.userId || this.userId,
timestamp: event.timestamp || new Date(),
appVersion: process.env.NEXT_PUBLIC_APP_VERSION,
};
// Send to analytics service (GA, Mixpanel, etc.)
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', event.name, payload);
}
// Send to custom analytics
if (process.env.NEXT_PUBLIC_ANALYTICS_API) {
fetch(process.env.NEXT_PUBLIC_ANALYTICS_API, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
keepalive: true,
}).catch((error) => {
logger.error('Failed to send analytics:', error);
});
}
} catch (error) {
logger.error('Analytics error:', error);
}
}
// Predefined events
pageView(pageName: string, properties?: Record<string, any>) {
this.trackEvent({
name: 'page_view',
properties: { page: pageName, ...properties },
});
}
userAction(action: string, properties?: Record<string, any>) {
this.trackEvent({
name: 'user_action',
properties: { action, ...properties },
});
}
error(error: Error, properties?: Record<string, any>) {
this.trackEvent({
name: 'error',
properties: {
message: error.message,
stack: error.stack,
...properties,
},
});
}
performance(metric: string, value: number, properties?: Record<string, any>) {
this.trackEvent({
name: 'performance',
properties: { metric, value, ...properties },
});
}
}
export const analytics = new AnalyticsService();
🎯 Xulosa#
Senior Team Lead Checklist#
| Aspekt | Check |
|---|---|
| Architecture | ✅ Monorepo with Nx/Turborepo |
| State Management | ✅ Query + Zustand + React Hook Form |
| Performance | ✅ Code splitting, memoization, virtualization |
| Testing | ✅ Unit + Integration + E2E |
| CI/CD | ✅ Automated pipeline with quality gates |
| Security | ✅ CSP, headers, authentication |
| Monitoring | ✅ Performance + Error + Analytics |
| Documentation | ✅ API docs + Storybook + README |
| Team Practices | ✅ Code review + Standards + Guidelines |
Tech Stack Recommendation#
Framework: Next.js 14 (App Router)
Language: TypeScript 5+
State: TanStack Query + Zustand
Forms: React Hook Form + Zod
UI: Tailwind CSS + Radix UI
Testing: Jest + React Testing Library + Playwright
CI/CD: GitHub Actions + Vercel
Monitoring: Sentry + DataDog
Analytics: Google Analytics + Mixpanel
📌 Key Takeaways#
- Think Architecture First — Scalability and maintainability from day one
- Choose the Right Tools — Not every project needs a monorepo
- Automate Everything — CI/CD, testing, code quality
- Measure Everything — Performance, errors, user behavior
- Document Everything — Code, decisions, processes
- Lead by Example — Write quality code and mentor your team
- Continuous Learning — Stay up to date with ecosystem changes
- Balance Perfect and Pragmatic — Good enough is better than perfect
🔗 Ushbu qo‘llanmani do‘stlaringiz bilan ulashing va React mahoratingizni oshiring!
#React #NextJS #TypeScript #Frontend #Architecture #SeniorDeveloper #TechLead #WebDevelopment

No comments yet.