Frontend Architecture Skill
When to Use This Skill
Use this skill when you need to:
-
Design scalable application architecture - Structure large-scale frontend applications with maintainable patterns
-
Choose architectural patterns - Select appropriate design patterns (MVC, MVVM, Flux) for your use case
-
Implement state management - Design state architecture for complex applications
-
Structure component hierarchies - Create reusable, composable component systems
-
Optimize build processes - Configure bundlers and build tools for optimal performance
-
Plan testing strategies - Architect comprehensive testing approaches across layers
-
Design module systems - Implement code splitting, lazy loading, and module boundaries
-
Scale codebases - Establish conventions for growing teams and applications
-
Refactor legacy code - Migrate to modern architectural patterns
-
Performance optimization - Structure applications for optimal load times and runtime performance
Core Concepts
- Component Architecture
Component-based architecture is the foundation of modern frontend development, enabling modularity and reusability.
Component Design Principles
Single Responsibility Principle Each component should have one clear purpose:
// Bad: Component doing too much function UserDashboard() { const [user, setUser] = useState(null); const [posts, setPosts] = useState([]); const [notifications, setNotifications] = useState([]); const [settings, setSettings] = useState({});
// Mixing concerns: data fetching, rendering, business logic useEffect(() => { fetch('/api/user').then(r => r.json()).then(setUser); fetch('/api/posts').then(r => r.json()).then(setPosts); fetch('/api/notifications').then(r => r.json()).then(setNotifications); }, []);
return ( <div> <header>{user?.name}</header> <PostList posts={posts} /> <NotificationBell count={notifications.length} /> <SettingsPanel settings={settings} /> </div> ); }
// Good: Separated concerns function UserDashboard() { return ( <DashboardLayout> <UserHeader /> <UserPosts /> <UserNotifications /> <UserSettings /> </DashboardLayout> ); }
function UserPosts() { const { posts, loading } = useUserPosts();
if (loading) return <PostsLoading />; return <PostList posts={posts} />; }
Composition Over Inheritance
// Using composition for flexibility interface ButtonProps { children: React.ReactNode; onClick?: () => void; variant?: 'primary' | 'secondary'; }
function Button({ children, onClick, variant = 'primary' }: ButtonProps) {
return (
<button className={btn btn-${variant}} onClick={onClick}>
{children}
</button>
);
}
// Compose complex components function IconButton({ icon, ...props }: ButtonProps & { icon: string }) { return ( <Button {...props}> <Icon name={icon} /> {props.children} </Button> ); }
function LoadingButton({ loading, ...props }: ButtonProps & { loading: boolean }) { return ( <Button {...props} disabled={loading}> {loading ? <Spinner /> : props.children} </Button> ); }
Container vs Presentational Components
// Presentational Component (Pure UI) interface UserCardProps { user: User; onEdit: () => void; onDelete: () => void; }
function UserCard({ user, onEdit, onDelete }: UserCardProps) { return ( <div className="user-card"> <img src={user.avatar} alt={user.name} /> <h3>{user.name}</h3> <p>{user.email}</p> <div className="actions"> <button onClick={onEdit}>Edit</button> <button onClick={onDelete}>Delete</button> </div> </div> ); }
// Container Component (Logic & Data) function UserCardContainer({ userId }: { userId: string }) { const { data: user, isLoading } = useQuery(['user', userId], () => fetchUser(userId) ); const deleteMutation = useMutation(deleteUser); const navigate = useNavigate();
const handleEdit = () => navigate(/users/${userId}/edit);
const handleDelete = () => {
if (confirm('Delete user?')) {
deleteMutation.mutate(userId);
}
};
if (isLoading) return <Skeleton />; if (!user) return <ErrorState />;
return ( <UserCard user={user} onEdit={handleEdit} onDelete={handleDelete} /> ); }
- Separation of Concerns
Layer Architecture
┌─────────────────────────────────────┐ │ Presentation Layer │ │ (Components, Views, UI) │ ├─────────────────────────────────────┤ │ Application Layer │ │ (State Management, Routing, Hooks) │ ├─────────────────────────────────────┤ │ Domain Layer │ │ (Business Logic, Entities) │ ├─────────────────────────────────────┤ │ Infrastructure Layer │ │ (API, Storage, Services) │ └─────────────────────────────────────┘
Example Implementation:
// Domain Layer - Business entities and logic export class User { constructor( public id: string, public email: string, public name: string, public role: UserRole ) {}
canEditPost(post: Post): boolean { return this.role === 'admin' || post.authorId === this.id; }
get displayName(): string { return this.name || this.email.split('@')[0]; } }
// Infrastructure Layer - API communication export class UserRepository { constructor(private apiClient: ApiClient) {}
async findById(id: string): Promise<User> {
const data = await this.apiClient.get(/users/${id});
return new User(data.id, data.email, data.name, data.role);
}
async save(user: User): Promise<void> {
await this.apiClient.put(/users/${user.id}, {
email: user.email,
name: user.name,
role: user.role
});
}
}
// Application Layer - State management export function useUser(userId: string) { const repository = useUserRepository();
return useQuery({ queryKey: ['user', userId], queryFn: () => repository.findById(userId) }); }
// Presentation Layer - UI Component export function UserProfile({ userId }: { userId: string }) { const { data: user, isLoading } = useUser(userId);
if (isLoading) return <Loading />;
return ( <div> <h1>{user.displayName}</h1> <p>{user.email}</p> </div> ); }
Design Patterns
- Model-View-Controller (MVC)
// Model - Data and business logic class TodoModel { private todos: Todo[] = []; private observers: Set<(todos: Todo[]) => void> = new Set();
addTodo(text: string) { const todo = { id: Date.now(), text, completed: false }; this.todos.push(todo); this.notify(); }
toggleTodo(id: number) { const todo = this.todos.find(t => t.id === id); if (todo) { todo.completed = !todo.completed; this.notify(); } }
getTodos() { return [...this.todos]; }
subscribe(observer: (todos: Todo[]) => void) { this.observers.add(observer); return () => this.observers.delete(observer); }
private notify() { this.observers.forEach(observer => observer(this.getTodos())); } }
// Controller - Handles user input class TodoController { constructor(private model: TodoModel) {}
handleAddTodo(text: string) { if (text.trim()) { this.model.addTodo(text); } }
handleToggleTodo(id: number) { this.model.toggleTodo(id); } }
// View - React component function TodoView() { const [model] = useState(() => new TodoModel()); const [controller] = useState(() => new TodoController(model)); const [todos, setTodos] = useState(model.getTodos()); const [inputValue, setInputValue] = useState('');
useEffect(() => { return model.subscribe(setTodos); }, [model]);
const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); controller.handleAddTodo(inputValue); setInputValue(''); };
return ( <div> <form onSubmit={handleSubmit}> <input value={inputValue} onChange={e => setInputValue(e.target.value)} /> <button type="submit">Add</button> </form> <ul> {todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => controller.handleToggleTodo(todo.id)} /> {todo.text} </li> ))} </ul> </div> ); }
- Model-View-ViewModel (MVVM)
// Model interface Task { id: string; title: string; completed: boolean; dueDate: Date; }
class TaskService { async fetchTasks(): Promise<Task[]> { const response = await fetch('/api/tasks'); return response.json(); }
async updateTask(id: string, updates: Partial<Task>): Promise<Task> {
const response = await fetch(/api/tasks/${id}, {
method: 'PATCH',
body: JSON.stringify(updates)
});
return response.json();
}
}
// ViewModel class TaskListViewModel { private tasks = signal<Task[]>([]); private loading = signal(false); private filter = signal<'all' | 'active' | 'completed'>('all');
constructor(private service: TaskService) {}
// Computed values get filteredTasks() { return computed(() => { const filterValue = this.filter.value; const tasksValue = this.tasks.value;
if (filterValue === 'active') {
return tasksValue.filter(t => !t.completed);
}
if (filterValue === 'completed') {
return tasksValue.filter(t => t.completed);
}
return tasksValue;
});
}
get stats() { return computed(() => { const tasksValue = this.tasks.value; return { total: tasksValue.length, completed: tasksValue.filter(t => t.completed).length, active: tasksValue.filter(t => !t.completed).length }; }); }
// Commands async loadTasks() { this.loading.value = true; try { this.tasks.value = await this.service.fetchTasks(); } finally { this.loading.value = false; } }
async toggleTask(id: string) { const task = this.tasks.value.find(t => t.id === id); if (!task) return;
const updated = await this.service.updateTask(id, {
completed: !task.completed
});
this.tasks.value = this.tasks.value.map(t =>
t.id === id ? updated : t
);
}
setFilter(filter: 'all' | 'active' | 'completed') { this.filter.value = filter; } }
// View function TaskListView() { const [viewModel] = useState(() => new TaskListViewModel(new TaskService()) );
useEffect(() => { viewModel.loadTasks(); }, [viewModel]);
const tasks = useSignal(viewModel.filteredTasks); const stats = useSignal(viewModel.stats);
return ( <div> <div className="stats"> <span>Total: {stats.total}</span> <span>Active: {stats.active}</span> <span>Completed: {stats.completed}</span> </div>
<div className="filters">
<button onClick={() => viewModel.setFilter('all')}>All</button>
<button onClick={() => viewModel.setFilter('active')}>Active</button>
<button onClick={() => viewModel.setFilter('completed')}>Completed</button>
</div>
<ul>
{tasks.map(task => (
<li key={task.id}>
<input
type="checkbox"
checked={task.completed}
onChange={() => viewModel.toggleTask(task.id)}
/>
{task.title}
</li>
))}
</ul>
</div>
); }
- Flux Architecture
// Actions enum ActionType { ADD_ITEM = 'ADD_ITEM', REMOVE_ITEM = 'REMOVE_ITEM', UPDATE_QUANTITY = 'UPDATE_QUANTITY', CLEAR_CART = 'CLEAR_CART' }
interface Action { type: ActionType; payload?: any; }
class CartActions { static addItem(item: CartItem): Action { return { type: ActionType.ADD_ITEM, payload: item }; }
static removeItem(itemId: string): Action { return { type: ActionType.REMOVE_ITEM, payload: itemId }; }
static updateQuantity(itemId: string, quantity: number): Action { return { type: ActionType.UPDATE_QUANTITY, payload: { itemId, quantity } }; } }
// Dispatcher class Dispatcher { private callbacks: Set<(action: Action) => void> = new Set();
register(callback: (action: Action) => void) { this.callbacks.add(callback); return () => this.callbacks.delete(callback); }
dispatch(action: Action) { this.callbacks.forEach(callback => callback(action)); } }
const dispatcher = new Dispatcher();
// Store class CartStore { private items: Map<string, CartItem> = new Map(); private listeners: Set<() => void> = new Set();
constructor() { dispatcher.register(this.handleAction.bind(this)); }
private handleAction(action: Action) { switch (action.type) { case ActionType.ADD_ITEM: this.addItem(action.payload); break; case ActionType.REMOVE_ITEM: this.removeItem(action.payload); break; case ActionType.UPDATE_QUANTITY: this.updateQuantity(action.payload.itemId, action.payload.quantity); break; case ActionType.CLEAR_CART: this.clear(); break; } }
private addItem(item: CartItem) { const existing = this.items.get(item.id); if (existing) { existing.quantity += item.quantity; } else { this.items.set(item.id, { ...item }); } this.emitChange(); }
private removeItem(itemId: string) { this.items.delete(itemId); this.emitChange(); }
private updateQuantity(itemId: string, quantity: number) { const item = this.items.get(itemId); if (item) { item.quantity = quantity; this.emitChange(); } }
private clear() { this.items.clear(); this.emitChange(); }
getItems(): CartItem[] { return Array.from(this.items.values()); }
getTotal(): number { return Array.from(this.items.values()) .reduce((sum, item) => sum + item.price * item.quantity, 0); }
subscribe(listener: () => void) { this.listeners.add(listener); return () => this.listeners.delete(listener); }
private emitChange() { this.listeners.forEach(listener => listener()); } }
const cartStore = new CartStore();
// View function ShoppingCart() { const [items, setItems] = useState(cartStore.getItems()); const [total, setTotal] = useState(cartStore.getTotal());
useEffect(() => { return cartStore.subscribe(() => { setItems(cartStore.getItems()); setTotal(cartStore.getTotal()); }); }, []);
const handleAddItem = (item: CartItem) => { dispatcher.dispatch(CartActions.addItem(item)); };
const handleRemoveItem = (itemId: string) => { dispatcher.dispatch(CartActions.removeItem(itemId)); };
return ( <div> <h2>Cart Total: ${total.toFixed(2)}</h2> <ul> {items.map(item => ( <li key={item.id}> {item.name} - ${item.price} x {item.quantity} <button onClick={() => handleRemoveItem(item.id)}>Remove</button> </li> ))} </ul> </div> ); }
- Observer Pattern
interface Observer<T> { update(data: T): void; }
class Subject<T> { private observers: Set<Observer<T>> = new Set();
attach(observer: Observer<T>): () => void { this.observers.add(observer); return () => this.observers.delete(observer); }
notify(data: T): void { this.observers.forEach(observer => observer.update(data)); } }
// Application example interface UserData { id: string; name: string; isOnline: boolean; }
class UserPresenceService extends Subject<UserData> { private socket: WebSocket;
constructor() { super(); this.socket = new WebSocket('wss://api.example.com/presence');
this.socket.onmessage = (event) => {
const userData = JSON.parse(event.data);
this.notify(userData);
};
}
updatePresence(isOnline: boolean) { this.socket.send(JSON.stringify({ isOnline })); } }
// Observer components class UserStatusObserver implements Observer<UserData> { constructor(private userId: string, private callback: (isOnline: boolean) => void) {}
update(data: UserData): void { if (data.id === this.userId) { this.callback(data.isOnline); } } }
function UserStatus({ userId }: { userId: string }) { const [isOnline, setIsOnline] = useState(false); const service = useUserPresenceService();
useEffect(() => { const observer = new UserStatusObserver(userId, setIsOnline); return service.attach(observer); }, [userId, service]);
return (
<span className={status ${isOnline ? 'online' : 'offline'}}>
{isOnline ? 'Online' : 'Offline'}
</span>
);
}
- Factory Pattern
// Abstract factory for form inputs interface FormInput { render(): JSX.Element; validate(): boolean; getValue(): any; }
class TextInputFactory { create(config: TextInputConfig): FormInput { return new TextInput(config); } }
class SelectInputFactory { create(config: SelectInputConfig): FormInput { return new SelectInput(config); } }
class DateInputFactory { create(config: DateInputConfig): FormInput { return new DateInput(config); } }
// Form builder using factory class FormBuilder { private factories = new Map<string, any>([ ['text', new TextInputFactory()], ['email', new TextInputFactory()], ['select', new SelectInputFactory()], ['date', new DateInputFactory()] ]);
createField(type: string, config: any): FormInput {
const factory = this.factories.get(type);
if (!factory) {
throw new Error(Unknown field type: ${type});
}
return factory.create(config);
}
buildForm(schema: FormSchema): FormInput[] { return schema.fields.map(field => this.createField(field.type, field.config) ); } }
// Usage function DynamicForm({ schema }: { schema: FormSchema }) { const [builder] = useState(() => new FormBuilder()); const [fields] = useState(() => builder.buildForm(schema));
const handleSubmit = (e: React.FormEvent) => { e.preventDefault();
const isValid = fields.every(field => field.validate());
if (isValid) {
const values = fields.map(field => field.getValue());
console.log('Form values:', values);
}
};
return ( <form onSubmit={handleSubmit}> {fields.map((field, index) => ( <div key={index}>{field.render()}</div> ))} <button type="submit">Submit</button> </form> ); }
- Module Pattern
// Revealing module pattern const AuthModule = (() => { // Private state let currentUser: User | null = null; const listeners: Set<(user: User | null) => void> = new Set();
// Private methods function notifyListeners() { listeners.forEach(listener => listener(currentUser)); }
function storeToken(token: string) { localStorage.setItem('auth_token', token); }
function clearToken() { localStorage.removeItem('auth_token'); }
// Public API return { async login(email: string, password: string) { const response = await fetch('/api/auth/login', { method: 'POST', body: JSON.stringify({ email, password }) });
const { user, token } = await response.json();
currentUser = user;
storeToken(token);
notifyListeners();
return user;
},
logout() {
currentUser = null;
clearToken();
notifyListeners();
},
getCurrentUser() {
return currentUser;
},
isAuthenticated() {
return currentUser !== null;
},
subscribe(listener: (user: User | null) => void) {
listeners.add(listener);
return () => listeners.delete(listener);
}
}; })();
// Usage in React function useAuth() { const [user, setUser] = useState(AuthModule.getCurrentUser());
useEffect(() => { return AuthModule.subscribe(setUser); }, []);
return { user, isAuthenticated: AuthModule.isAuthenticated(), login: AuthModule.login, logout: AuthModule.logout }; }
State Management
- Local vs Global State
// Local state - Component-specific function SearchBar() { const [query, setQuery] = useState(''); // Local to this component const [isFocused, setIsFocused] = useState(false);
return ( <input value={query} onChange={e => setQuery(e.target.value)} onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} /> ); }
// Lifted state - Shared between siblings function SearchPage() { const [searchResults, setSearchResults] = useState([]);
const handleSearch = async (query: string) => { const results = await searchAPI(query); setSearchResults(results); };
return ( <div> <SearchBar onSearch={handleSearch} /> <SearchResults results={searchResults} /> <SearchFilters results={searchResults} /> </div> ); }
// Global state - Application-wide const UserContext = createContext<UserContextValue>(null);
function App() { const [user, setUser] = useState<User | null>(null);
return ( <UserContext.Provider value={{ user, setUser }}> <Router /> </UserContext.Provider> ); }
// Server state - Managed by React Query function useProducts() { return useQuery({ queryKey: ['products'], queryFn: fetchProducts, staleTime: 5 * 60 * 1000 // 5 minutes }); }
- Unidirectional Data Flow
// Redux-style unidirectional flow interface AppState { user: User | null; cart: CartItem[]; notifications: Notification[]; }
type AppAction = | { type: 'user/login'; payload: User } | { type: 'user/logout' } | { type: 'cart/addItem'; payload: CartItem } | { type: 'cart/removeItem'; payload: string } | { type: 'notifications/add'; payload: Notification };
function appReducer(state: AppState, action: AppAction): AppState { switch (action.type) { case 'user/login': return { ...state, user: action.payload };
case 'user/logout':
return { ...state, user: null, cart: [] };
case 'cart/addItem':
return {
...state,
cart: [...state.cart, action.payload]
};
case 'cart/removeItem':
return {
...state,
cart: state.cart.filter(item => item.id !== action.payload)
};
case 'notifications/add':
return {
...state,
notifications: [...state.notifications, action.payload]
};
default:
return state;
} }
// Store setup const AppContext = createContext<{ state: AppState; dispatch: React.Dispatch<AppAction>; }>(null!);
function AppProvider({ children }: { children: React.ReactNode }) { const [state, dispatch] = useReducer(appReducer, { user: null, cart: [], notifications: [] });
return ( <AppContext.Provider value={{ state, dispatch }}> {children} </AppContext.Provider> ); }
// Selectors function useUser() { const { state } = useContext(AppContext); return state.user; }
function useCart() { const { state, dispatch } = useContext(AppContext);
return { items: state.cart, addItem: (item: CartItem) => dispatch({ type: 'cart/addItem', payload: item }), removeItem: (id: string) => dispatch({ type: 'cart/removeItem', payload: id }) }; }
- State Management Patterns
Zustand - Simple State Management
import create from 'zustand'; import { devtools, persist } from 'zustand/middleware';
interface TodoState { todos: Todo[]; filter: 'all' | 'active' | 'completed'; addTodo: (text: string) => void; toggleTodo: (id: string) => void; removeTodo: (id: string) => void; setFilter: (filter: 'all' | 'active' | 'completed') => void; filteredTodos: () => Todo[]; }
const useTodoStore = create<TodoState>()( devtools( persist( (set, get) => ({ todos: [], filter: 'all',
addTodo: (text) =>
set((state) => ({
todos: [...state.todos, {
id: crypto.randomUUID(),
text,
completed: false
}]
})),
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map(todo =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
})),
removeTodo: (id) =>
set((state) => ({
todos: state.todos.filter(todo => todo.id !== id)
})),
setFilter: (filter) => set({ filter }),
filteredTodos: () => {
const { todos, filter } = get();
if (filter === 'active') return todos.filter(t => !t.completed);
if (filter === 'completed') return todos.filter(t => t.completed);
return todos;
}
}),
{ name: 'todo-storage' }
)
) );
// Usage function TodoList() { const todos = useTodoStore(state => state.filteredTodos()); const toggleTodo = useTodoStore(state => state.toggleTodo);
return ( <ul> {todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => toggleTodo(todo.id)} /> {todo.text} </li> ))} </ul> ); }
Jotai - Atomic State Management
import { atom, useAtom, useAtomValue, useSetAtom } from 'jotai'; import { atomWithStorage } from 'jotai/utils';
// Primitive atoms const userAtom = atom<User | null>(null); const cartItemsAtom = atomWithStorage<CartItem[]>('cart', []);
// Derived atoms const cartTotalAtom = atom((get) => { const items = get(cartItemsAtom); return items.reduce((sum, item) => sum + item.price * item.quantity, 0); });
const cartCountAtom = atom((get) => { const items = get(cartItemsAtom); return items.reduce((sum, item) => sum + item.quantity, 0); });
// Write-only atoms (actions) const addToCartAtom = atom( null, (get, set, item: CartItem) => { const items = get(cartItemsAtom); const existing = items.find(i => i.id === item.id);
if (existing) {
set(cartItemsAtom, items.map(i =>
i.id === item.id
? { ...i, quantity: i.quantity + item.quantity }
: i
));
} else {
set(cartItemsAtom, [...items, item]);
}
} );
// Usage function ShoppingCart() { const items = useAtomValue(cartItemsAtom); const total = useAtomValue(cartTotalAtom); const count = useAtomValue(cartCountAtom); const addToCart = useSetAtom(addToCartAtom);
return ( <div> <h2>Cart ({count} items) - ${total.toFixed(2)}</h2> {/* ... */} </div> ); }
Module Systems
- ES Modules
// modules/logger.ts export interface Logger { info(message: string): void; warn(message: string): void; error(message: string): void; }
export class ConsoleLogger implements Logger {
info(message: string) {
console.log([INFO] ${message});
}
warn(message: string) {
console.warn([WARN] ${message});
}
error(message: string) {
console.error([ERROR] ${message});
}
}
export default new ConsoleLogger();
// modules/api-client.ts import logger, { Logger } from './logger';
export class ApiClient { constructor( private baseURL: string, private logger: Logger = logger ) {}
async get<T>(path: string): Promise<T> {
this.logger.info(GET ${path});
const response = await fetch(${this.baseURL}${path});
return response.json();
}
}
// app.ts import { ApiClient } from './modules/api-client'; import logger from './modules/logger';
const api = new ApiClient('https://api.example.com', logger);
- Code Splitting
// Route-based code splitting import { lazy, Suspense } from 'react'; import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home')); const Dashboard = lazy(() => import('./pages/Dashboard')); const Settings = lazy(() => import('./pages/Settings'));
function App() { return ( <BrowserRouter> <Suspense fallback={<PageLoader />}> <Routes> <Route path="/" element={<Home />} /> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/settings" element={<Settings />} /> </Routes> </Suspense> </BrowserRouter> ); }
// Component-based code splitting const HeavyChart = lazy(() => import('./components/HeavyChart'));
function Analytics() { const [showChart, setShowChart] = useState(false);
return ( <div> <button onClick={() => setShowChart(true)}>Show Chart</button>
{showChart && (
<Suspense fallback={<div>Loading chart...</div>}>
<HeavyChart data={chartData} />
</Suspense>
)}
</div>
); }
// Dynamic imports with loading states function DataTable() { const [ExcelExporter, setExporter] = useState<any>(null); const [loading, setLoading] = useState(false);
const handleExport = async () => { if (!ExcelExporter) { setLoading(true); const module = await import('./utils/excel-exporter'); setExporter(() => module.ExcelExporter); setLoading(false); }
if (ExcelExporter) {
new ExcelExporter().export(data);
}
};
return ( <div> <button onClick={handleExport} disabled={loading}> {loading ? 'Loading...' : 'Export to Excel'} </button> </div> ); }
- Lazy Loading
// Image lazy loading function LazyImage({ src, alt }: { src: string; alt: string }) { const [imageSrc, setImageSrc] = useState<string>(); const imgRef = useRef<HTMLImageElement>(null);
useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { setImageSrc(src); observer.disconnect(); } }, { rootMargin: '100px' } );
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => observer.disconnect();
}, [src]);
return ( <img ref={imgRef} src={imageSrc || 'placeholder.jpg'} alt={alt} loading="lazy" /> ); }
// Data lazy loading with infinite scroll function InfiniteList() { const [page, setPage] = useState(1); const { data, isLoading, hasNextPage } = useInfiniteQuery({ queryKey: ['items', page], queryFn: ({ pageParam = 1 }) => fetchItems(pageParam), getNextPageParam: (lastPage, pages) => lastPage.hasMore ? pages.length + 1 : undefined });
const loadMoreRef = useRef<HTMLDivElement>(null);
useEffect(() => { const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting && hasNextPage && !isLoading) { setPage(prev => prev + 1); } } );
if (loadMoreRef.current) {
observer.observe(loadMoreRef.current);
}
return () => observer.disconnect();
}, [hasNextPage, isLoading]);
return ( <div> {data?.pages.map((page, i) => ( <div key={i}> {page.items.map(item => ( <ItemCard key={item.id} item={item} /> ))} </div> ))} {hasNextPage && <div ref={loadMoreRef}>Loading more...</div>} </div> ); }
Build Tools
- Webpack Configuration
// webpack.config.js const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
module.exports = (env, argv) => { const isDevelopment = argv.mode === 'development';
return { entry: { main: './src/index.tsx', vendor: ['react', 'react-dom'] },
output: {
path: path.resolve(__dirname, 'dist'),
filename: isDevelopment
? '[name].js'
: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].chunk.js',
clean: true
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
},
common: {
minChunks: 2,
priority: 5,
reuseExistingChunk: true
}
}
},
runtimeChunk: 'single'
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
},
{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024 // 8kb
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
}),
!isDevelopment && new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
}),
process.env.ANALYZE && new BundleAnalyzerPlugin()
].filter(Boolean),
devServer: {
port: 3000,
hot: true,
historyApiFallback: true
}
}; };
- Vite Configuration
// vite.config.ts import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import { visualizer } from 'rollup-plugin-visualizer'; import path from 'path';
export default defineConfig({ plugins: [ react(), visualizer({ template: 'treemap', open: true, gzipSize: true }) ],
resolve: { alias: { '@': path.resolve(__dirname, './src'), '@components': path.resolve(__dirname, './src/components'), '@utils': path.resolve(__dirname, './src/utils') } },
build: { rollupOptions: { output: { manualChunks: { 'react-vendor': ['react', 'react-dom', 'react-router-dom'], 'ui-vendor': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'], 'utils': ['date-fns', 'lodash-es'] } } }, chunkSizeWarningLimit: 1000 },
server: { port: 3000, proxy: { '/api': { target: 'http://localhost:8000', changeOrigin: true, rewrite: (path) => path.replace(/^/api/, '') } } } });
Testing Architecture
- Testing Pyramid
// Unit tests - Test individual functions/components describe('calculateCartTotal', () => { it('should sum item prices', () => { const items = [ { price: 10, quantity: 2 }, { price: 5, quantity: 3 } ]; expect(calculateCartTotal(items)).toBe(35); });
it('should handle empty cart', () => { expect(calculateCartTotal([])).toBe(0); }); });
// Component tests describe('UserCard', () => { it('should render user information', () => { const user = { name: 'John', email: 'john@example.com' }; render(<UserCard user={user} />);
expect(screen.getByText('John')).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
});
it('should call onEdit when edit button is clicked', () => { const onEdit = jest.fn(); render(<UserCard user={user} onEdit={onEdit} />);
fireEvent.click(screen.getByText('Edit'));
expect(onEdit).toHaveBeenCalled();
}); });
// Integration tests - Test component interactions describe('LoginFlow', () => { it('should login user and redirect to dashboard', async () => { const { user } = renderWithRouter(<LoginPage />);
await user.type(screen.getByLabelText('Email'), 'user@example.com');
await user.type(screen.getByLabelText('Password'), 'password123');
await user.click(screen.getByText('Login'));
await waitFor(() => {
expect(screen.getByText('Dashboard')).toBeInTheDocument();
});
}); });
// E2E tests - Test complete user flows describe('Checkout Flow', () => { it('should complete purchase', async () => { await page.goto('http://localhost:3000');
// Add items to cart
await page.click('[data-testid="product-1"]');
await page.click('[data-testid="add-to-cart"]');
// Go to checkout
await page.click('[data-testid="cart-icon"]');
await page.click('[data-testid="checkout"]');
// Fill shipping info
await page.fill('[name="address"]', '123 Main St');
await page.fill('[name="city"]', 'New York');
// Complete payment
await page.fill('[name="cardNumber"]', '4242424242424242');
await page.click('[data-testid="place-order"]');
// Verify success
await expect(page.locator('[data-testid="order-confirmation"]'))
.toBeVisible();
}); });
- Testing Patterns
// Test utilities export function renderWithProviders( ui: React.ReactElement, options?: { preloadedState?: Partial<AppState>; store?: AppStore; } ) { const store = options?.store || createStore(options?.preloadedState);
function Wrapper({ children }: { children: React.ReactNode }) { return ( <Provider store={store}> <QueryClientProvider client={queryClient}> <BrowserRouter> {children} </BrowserRouter> </QueryClientProvider> </Provider> ); }
return { ...render(ui, { wrapper: Wrapper }), store }; }
// Mock factories export function createMockUser(overrides?: Partial<User>): User { return { id: '1', email: 'test@example.com', name: 'Test User', role: 'user', ...overrides }; }
// Custom matchers expect.extend({ toHaveBeenCalledWithUser(received, user: User) { const pass = received.mock.calls.some((call: any[]) => call.some(arg => arg?.id === user.id) );
return {
pass,
message: () =>
pass
? `Expected not to have been called with user ${user.id}`
: `Expected to have been called with user ${user.id}`
};
} });
Performance Optimization
- Code Splitting Strategies
// Route-based splitting const routes = [ { path: '/', component: lazy(() => import('./pages/Home')) }, { path: '/dashboard', component: lazy(() => import('./pages/Dashboard')) } ];
// Feature-based splitting const FeatureToggle = ({ feature, children }: FeatureToggleProps) => { const [Component, setComponent] = useState<React.ComponentType | null>(null);
useEffect(() => {
if (feature.enabled) {
import(./features/${feature.name}).then(module => {
setComponent(() => module.default);
});
}
}, [feature]);
if (!Component) return null; return <Component>{children}</Component>; };
// Library splitting const Editor = lazy(() => import(/* webpackChunkName: "editor" */ '@monaco-editor/react') );
- Tree Shaking
// Good - Named imports enable tree shaking import { debounce } from 'lodash-es';
// Bad - Imports entire library import _ from 'lodash';
// Configure in package.json { "sideEffects": [ ".css", ".scss" ] }
// Mark pure functions for tree shaking /#PURE/ export function createLogger() { return console.log; }
- Caching Strategies
// Service Worker caching // sw.js const CACHE_NAME = 'app-v1'; const STATIC_ASSETS = [ '/', '/styles.css', '/app.js' ];
self.addEventListener('install', (event) => { event.waitUntil( caches.open(CACHE_NAME).then((cache) => { return cache.addAll(STATIC_ASSETS); }) ); });
self.addEventListener('fetch', (event) => { event.respondWith( caches.match(event.request).then((response) => { return response || fetch(event.request); }) ); });
// HTTP caching headers (server-side) app.use('/static', express.static('public', { maxAge: '1y', immutable: true }));
// React Query caching const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 5 * 60 * 1000, // 5 minutes cacheTime: 10 * 60 * 1000, // 10 minutes refetchOnWindowFocus: false } } });
Scalability
- Folder Structure
src/ ├── features/ # Feature-based organization │ ├── auth/ │ │ ├── components/ │ │ │ ├── LoginForm.tsx │ │ │ └── SignupForm.tsx │ │ ├── hooks/ │ │ │ └── useAuth.ts │ │ ├── services/ │ │ │ └── authService.ts │ │ ├── types/ │ │ │ └── auth.types.ts │ │ └── index.ts │ ├── products/ │ └── cart/ ├── shared/ # Shared across features │ ├── components/ │ │ ├── Button/ │ │ ├── Modal/ │ │ └── Form/ │ ├── hooks/ │ ├── utils/ │ └── types/ ├── core/ # Core app functionality │ ├── api/ │ ├── config/ │ ├── router/ │ └── store/ ├── layouts/ ├── pages/ └── assets/
- Naming Conventions
// Components - PascalCase export function UserProfile() {} export function ProductCard() {}
// Hooks - camelCase with 'use' prefix export function useAuth() {} export function useLocalStorage() {}
// Utilities - camelCase export function formatDate() {} export function debounce() {}
// Constants - UPPER_SNAKE_CASE export const API_BASE_URL = 'https://api.example.com'; export const MAX_FILE_SIZE = 5 * 1024 * 1024;
// Types/Interfaces - PascalCase export interface User {} export type UserRole = 'admin' | 'user';
// Files UserProfile.tsx // Component UserProfile.test.tsx // Test UserProfile.module.css // CSS Module useAuth.ts // Hook userService.ts // Service user.types.ts // Types
- Dependency Management
// Dependency injection for testability interface Services { api: ApiClient; storage: StorageService; logger: Logger; }
const ServicesContext = createContext<Services>(null!);
export function useServices() { return useContext(ServicesContext); }
// Usage in components function UserProfile() { const { api, logger } = useServices();
const loadUser = async () => { try { const user = await api.get('/user'); return user; } catch (error) { logger.error('Failed to load user', error); } }; }
// Testing with mocked services test('loads user data', () => { const mockApi = { get: jest.fn().mockResolvedValue(mockUser) }; const mockLogger = { error: jest.fn() };
render( <ServicesContext.Provider value={{ api: mockApi, logger: mockLogger }}> <UserProfile /> </ServicesContext.Provider> ); });
Best Practices
Component Design
-
Keep components small and focused
-
Prefer composition over inheritance
-
Use TypeScript for type safety
-
Implement proper prop validation
State Management
-
Choose the right level of state (local vs global)
-
Avoid prop drilling with context or state libraries
-
Use immutable updates
-
Separate server state from client state
Performance
-
Implement code splitting and lazy loading
-
Optimize bundle size with tree shaking
-
Use memoization appropriately
-
Implement proper caching strategies
Testing
-
Follow the testing pyramid
-
Write meaningful tests, not just for coverage
-
Use proper test utilities and helpers
-
Mock external dependencies
Scalability
-
Use consistent folder structure
-
Follow naming conventions
-
Implement proper module boundaries
-
Document architectural decisions
Related Skills
-
react-patterns - React-specific patterns and best practices
-
typescript-architecture - TypeScript design patterns
-
performance-optimization - Advanced performance techniques
-
testing-strategies - Comprehensive testing approaches