
Frontend Architecture
- 530 installs
- 61 repo stars
- Updated June 13, 2026
- manutej/luxor-claude-marketplace
frontend-architecture is an agent skill that defines frontend layering, state boundaries, module federation, routing, and performance budgets for developers scaling multi-module web applications.
About
frontend-architecture is a manutej/luxor-claude-marketplace skill (version 1.0.0) for designing scalable frontend application structure in large TypeScript and React codebases. It guides component architecture with single-responsibility and composition-over-inheritance patterns, state management strategies for complex apps, module systems with code splitting and lazy loading, bundler performance tuning, testing strategies across layers, and migration from legacy patterns to MVC, MVVM, or Flux-style designs. The skill addresses module federation boundaries, routing strategy, performance budgets, and conventions for growing repositories documented in a 45 KB SKILL.md with companion EXAMPLES.md. Developers reach for frontend-architecture when a SPA outgrows flat component folders, when teams need explicit state boundaries, or when planning module federation across packages.
- Feature-sliced or domain folders
- State management boundaries
- Code splitting and lazy routes
- Shared design system integration
- Testing and observability hooks
Frontend Architecture by the numbers
- 530 all-time installs (skills.sh)
- +29 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #610 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/manutej/luxor-claude-marketplace --skill frontend-architectureAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 530 |
|---|---|
| repo stars | ★ 61 |
| Last updated | June 13, 2026 |
| Repository | manutej/luxor-claude-marketplace ↗ |
How do you structure scalable frontend architecture?
Define frontend layering, state boundaries, module federation, routing strategy, and performance budgets for scalable multi-team web applications.
Who is it for?
Senior frontend developers architecting large React or TypeScript SPAs that need module federation, state boundaries, and performance budgets.
Skip if: Small single-page prototypes where a flat components folder and one state library need no formal architecture pass.
When should I use this skill?
Designing a new large frontend codebase, planning module federation, choosing MVC or Flux patterns, or refactoring legacy SPA structure.
What you get
Component hierarchy plan, state boundary map, module federation layout, routing strategy, and documented performance budgets.
- architecture decision record
- module boundary map
- performance budget document
By the numbers
- Skill metadata version 1.0.0 in luxor-frontend-essentials marketplace
- SKILL.md is approximately 45 KB with companion EXAMPLES.md at 64 KB
- Covers 3 architectural patterns: MVC, MVVM, and Flux
Files
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
1. 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}
/>
);
}2. 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
1. 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>
);
}2. 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>
);
}3. 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>
);
}4. 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>
);
}5. 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>
);
}6. 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
1. 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
});
}2. 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 })
};
}3. 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
1. 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);2. 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>
);
}3. 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
1. 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
}
};
};2. 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
1. 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();
});
});2. 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
1. 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')
);2. 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;
}3. 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
1. 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/2. 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 // Types3. 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
1. Component Design
- Keep components small and focused
- Prefer composition over inheritance
- Use TypeScript for type safety
- Implement proper prop validation
2. 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
3. Performance
- Implement code splitting and lazy loading
- Optimize bundle size with tree shaking
- Use memoization appropriately
- Implement proper caching strategies
4. Testing
- Follow the testing pyramid
- Write meaningful tests, not just for coverage
- Use proper test utilities and helpers
- Mock external dependencies
5. 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
Frontend Architecture Examples
A comprehensive collection of real-world architectural patterns and examples for building scalable frontend applications.
Table of Contents
1. E-Commerce Application Architecture 2. Social Media Dashboard 3. Multi-Tenant SaaS Platform 4. Real-Time Collaboration App 5. Micro-Frontend Architecture 6. Offline-First PWA 7. Admin Dashboard with RBAC 8. Form Builder Application 9. Data Visualization Platform 10. Plugin-Based Architecture 11. State Machine Architecture 12. Event-Driven Architecture 13. Layered Architecture Pattern 14. Repository Pattern Implementation 15. Feature Flag System 16. Advanced Caching Strategy 17. Scalable Testing Architecture
---
1. E-Commerce Application Architecture
A complete e-commerce platform demonstrating feature-based architecture, state management, and modular design.
Folder Structure
src/
├── features/
│ ├── products/
│ │ ├── components/
│ │ │ ├── ProductCard.tsx
│ │ │ ├── ProductGrid.tsx
│ │ │ ├── ProductDetail.tsx
│ │ │ └── ProductFilter.tsx
│ │ ├── hooks/
│ │ │ ├── useProducts.ts
│ │ │ ├── useProductFilters.ts
│ │ │ └── useProductSearch.ts
│ │ ├── services/
│ │ │ └── productService.ts
│ │ ├── store/
│ │ │ └── productStore.ts
│ │ └── types/
│ │ └── product.types.ts
│ ├── cart/
│ │ ├── components/
│ │ │ ├── Cart.tsx
│ │ │ ├── CartItem.tsx
│ │ │ └── CartSummary.tsx
│ │ ├── hooks/
│ │ │ └── useCart.ts
│ │ ├── store/
│ │ │ └── cartStore.ts
│ │ └── types/
│ │ └── cart.types.ts
│ ├── checkout/
│ │ ├── components/
│ │ ├── hooks/
│ │ └── services/
│ └── auth/
├── shared/
│ ├── components/
│ ├── hooks/
│ └── utils/
└── core/
├── api/
├── router/
└── store/Implementation
Product Feature
// features/products/types/product.types.ts
export interface Product {
id: string;
name: string;
description: string;
price: number;
images: string[];
category: string;
stock: number;
rating: number;
}
export interface ProductFilters {
category?: string;
minPrice?: number;
maxPrice?: number;
inStock?: boolean;
rating?: number;
}
// features/products/services/productService.ts
export class ProductService {
constructor(private apiClient: ApiClient) {}
async getProducts(filters?: ProductFilters): Promise<Product[]> {
const params = new URLSearchParams();
if (filters?.category) params.append('category', filters.category);
if (filters?.minPrice) params.append('minPrice', String(filters.minPrice));
if (filters?.maxPrice) params.append('maxPrice', String(filters.maxPrice));
const response = await this.apiClient.get(`/products?${params}`);
return response.data;
}
async getProduct(id: string): Promise<Product> {
const response = await this.apiClient.get(`/products/${id}`);
return response.data;
}
async searchProducts(query: string): Promise<Product[]> {
const response = await this.apiClient.get(`/products/search?q=${query}`);
return response.data;
}
}
// features/products/store/productStore.ts
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
interface ProductState {
products: Product[];
selectedProduct: Product | null;
filters: ProductFilters;
loading: boolean;
error: string | null;
setProducts: (products: Product[]) => void;
setSelectedProduct: (product: Product | null) => void;
setFilters: (filters: ProductFilters) => void;
setLoading: (loading: boolean) => void;
setError: (error: string | null) => void;
}
export const useProductStore = create<ProductState>()(
devtools((set) => ({
products: [],
selectedProduct: null,
filters: {},
loading: false,
error: null,
setProducts: (products) => set({ products }),
setSelectedProduct: (selectedProduct) => set({ selectedProduct }),
setFilters: (filters) => set({ filters }),
setLoading: (loading) => set({ loading }),
setError: (error) => set({ error })
}))
);
// features/products/hooks/useProducts.ts
export function useProducts(filters?: ProductFilters) {
const productService = useProductService();
const setProducts = useProductStore(state => state.setProducts);
const setLoading = useProductStore(state => state.setLoading);
const setError = useProductStore(state => state.setError);
return useQuery({
queryKey: ['products', filters],
queryFn: async () => {
setLoading(true);
try {
const products = await productService.getProducts(filters);
setProducts(products);
return products;
} catch (error) {
setError(error.message);
throw error;
} finally {
setLoading(false);
}
},
staleTime: 5 * 60 * 1000
});
}
// features/products/components/ProductGrid.tsx
export function ProductGrid() {
const filters = useProductStore(state => state.filters);
const { data: products, isLoading } = useProducts(filters);
if (isLoading) return <ProductGridSkeleton />;
return (
<div className="product-grid">
{products?.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}Cart Feature
// features/cart/store/cartStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface CartItem {
product: Product;
quantity: number;
}
interface CartState {
items: CartItem[];
addItem: (product: Product, quantity: number) => void;
removeItem: (productId: string) => void;
updateQuantity: (productId: string, quantity: number) => void;
clear: () => void;
total: () => number;
itemCount: () => number;
}
export const useCartStore = create<CartState>()(
persist(
(set, get) => ({
items: [],
addItem: (product, quantity) =>
set((state) => {
const existing = state.items.find(
item => item.product.id === product.id
);
if (existing) {
return {
items: state.items.map(item =>
item.product.id === product.id
? { ...item, quantity: item.quantity + quantity }
: item
)
};
}
return {
items: [...state.items, { product, quantity }]
};
}),
removeItem: (productId) =>
set((state) => ({
items: state.items.filter(item => item.product.id !== productId)
})),
updateQuantity: (productId, quantity) =>
set((state) => ({
items: state.items.map(item =>
item.product.id === productId
? { ...item, quantity }
: item
)
})),
clear: () => set({ items: [] }),
total: () => {
const items = get().items;
return items.reduce(
(sum, item) => sum + item.product.price * item.quantity,
0
);
},
itemCount: () => {
const items = get().items;
return items.reduce((sum, item) => sum + item.quantity, 0);
}
}),
{ name: 'cart-storage' }
)
);
// features/cart/components/Cart.tsx
export function Cart() {
const items = useCartStore(state => state.items);
const total = useCartStore(state => state.total());
const removeItem = useCartStore(state => state.removeItem);
const updateQuantity = useCartStore(state => state.updateQuantity);
return (
<div className="cart">
<h2>Shopping Cart</h2>
{items.length === 0 ? (
<EmptyCart />
) : (
<>
<div className="cart-items">
{items.map(item => (
<CartItem
key={item.product.id}
item={item}
onRemove={() => removeItem(item.product.id)}
onUpdateQuantity={(qty) =>
updateQuantity(item.product.id, qty)
}
/>
))}
</div>
<CartSummary total={total} />
<Link to="/checkout">
<Button>Proceed to Checkout</Button>
</Link>
</>
)}
</div>
);
}---
2. Social Media Dashboard
Real-time updates with WebSocket integration and optimistic updates.
Architecture
// core/websocket/WebSocketManager.ts
export class WebSocketManager {
private socket: WebSocket | null = null;
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
private listeners = new Map<string, Set<(data: any) => void>>();
connect(url: string, token: string) {
this.socket = new WebSocket(`${url}?token=${token}`);
this.socket.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
};
this.socket.onmessage = (event) => {
const message = JSON.parse(event.data);
this.notifyListeners(message.type, message.data);
};
this.socket.onclose = () => {
console.log('WebSocket disconnected');
this.reconnect(url, token);
};
this.socket.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
subscribe(eventType: string, callback: (data: any) => void) {
if (!this.listeners.has(eventType)) {
this.listeners.set(eventType, new Set());
}
this.listeners.get(eventType)!.add(callback);
return () => {
this.listeners.get(eventType)?.delete(callback);
};
}
send(eventType: string, data: any) {
if (this.socket?.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify({ type: eventType, data }));
}
}
private notifyListeners(eventType: string, data: any) {
this.listeners.get(eventType)?.forEach(callback => callback(data));
}
private reconnect(url: string, token: string) {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
setTimeout(() => {
console.log(`Reconnecting... attempt ${this.reconnectAttempts}`);
this.connect(url, token);
}, delay);
}
}
disconnect() {
this.socket?.close();
this.socket = null;
this.listeners.clear();
}
}
// features/feed/hooks/useFeed.ts
export function useFeed() {
const queryClient = useQueryClient();
const ws = useWebSocketManager();
const { data: posts, isLoading } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts
});
// Subscribe to real-time updates
useEffect(() => {
const unsubscribe = ws.subscribe('post:created', (newPost: Post) => {
queryClient.setQueryData<Post[]>(['posts'], (old = []) => [
newPost,
...old
]);
});
return unsubscribe;
}, [ws, queryClient]);
const createPost = useMutation({
mutationFn: async (content: string) => {
const tempId = `temp-${Date.now()}`;
const optimisticPost: Post = {
id: tempId,
content,
author: currentUser,
createdAt: new Date(),
likes: 0,
comments: []
};
// Optimistic update
queryClient.setQueryData<Post[]>(['posts'], (old = []) => [
optimisticPost,
...old
]);
try {
const post = await postService.create(content);
return post;
} catch (error) {
// Rollback on error
queryClient.setQueryData<Post[]>(['posts'], (old = []) =>
old.filter(p => p.id !== tempId)
);
throw error;
}
},
onSuccess: (newPost) => {
// Replace optimistic post with real post
queryClient.setQueryData<Post[]>(['posts'], (old = []) =>
old.map(p => p.id.startsWith('temp-') ? newPost : p)
);
}
});
const likePost = useMutation({
mutationFn: async (postId: string) => {
// Optimistic update
queryClient.setQueryData<Post[]>(['posts'], (old = []) =>
old.map(p =>
p.id === postId
? { ...p, likes: p.likes + 1, likedByCurrentUser: true }
: p
)
);
return postService.like(postId);
},
onError: (error, postId) => {
// Rollback on error
queryClient.setQueryData<Post[]>(['posts'], (old = []) =>
old.map(p =>
p.id === postId
? { ...p, likes: p.likes - 1, likedByCurrentUser: false }
: p
)
);
}
});
return {
posts,
isLoading,
createPost: createPost.mutate,
likePost: likePost.mutate
};
}---
3. Multi-Tenant SaaS Platform
Tenant isolation, feature flags, and role-based access control.
Implementation
// core/tenant/TenantContext.tsx
interface Tenant {
id: string;
name: string;
plan: 'free' | 'pro' | 'enterprise';
features: string[];
settings: Record<string, any>;
}
interface TenantContextValue {
tenant: Tenant | null;
hasFeature: (feature: string) => boolean;
getSetting: <T>(key: string, defaultValue: T) => T;
}
const TenantContext = createContext<TenantContextValue>(null!);
export function TenantProvider({ children }: { children: React.ReactNode }) {
const [tenant, setTenant] = useState<Tenant | null>(null);
useEffect(() => {
// Load tenant from subdomain or context
const subdomain = window.location.hostname.split('.')[0];
loadTenant(subdomain).then(setTenant);
}, []);
const hasFeature = useCallback(
(feature: string) => {
return tenant?.features.includes(feature) ?? false;
},
[tenant]
);
const getSetting = useCallback(
<T,>(key: string, defaultValue: T): T => {
return (tenant?.settings[key] as T) ?? defaultValue;
},
[tenant]
);
return (
<TenantContext.Provider value={{ tenant, hasFeature, getSetting }}>
{children}
</TenantContext.Provider>
);
}
export const useTenant = () => useContext(TenantContext);
// shared/components/FeatureGate.tsx
export function FeatureGate({
feature,
fallback,
children
}: {
feature: string;
fallback?: React.ReactNode;
children: React.ReactNode;
}) {
const { hasFeature } = useTenant();
if (!hasFeature(feature)) {
return <>{fallback || null}</>;
}
return <>{children}</>;
}
// Usage
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<FeatureGate feature="analytics">
<AnalyticsWidget />
</FeatureGate>
<FeatureGate
feature="advanced-reports"
fallback={<UpgradeBanner feature="Advanced Reports" />}
>
<AdvancedReports />
</FeatureGate>
</div>
);
}
// core/rbac/usePermissions.ts
export function usePermissions() {
const { user } = useAuth();
const hasPermission = useCallback(
(permission: string) => {
return user?.permissions.includes(permission) ?? false;
},
[user]
);
const hasRole = useCallback(
(role: string) => {
return user?.roles.includes(role) ?? false;
},
[user]
);
const hasAnyRole = useCallback(
(roles: string[]) => {
return roles.some(role => user?.roles.includes(role)) ?? false;
},
[user]
);
return { hasPermission, hasRole, hasAnyRole };
}
// shared/components/ProtectedRoute.tsx
export function ProtectedRoute({
children,
permission,
fallback
}: {
children: React.ReactNode;
permission?: string;
fallback?: React.ReactNode;
}) {
const { isAuthenticated } = useAuth();
const { hasPermission } = usePermissions();
if (!isAuthenticated) {
return <Navigate to="/login" />;
}
if (permission && !hasPermission(permission)) {
return <>{fallback || <AccessDenied />}</>;
}
return <>{children}</>;
}---
4. Real-Time Collaboration App
Operational transformation and conflict resolution for collaborative editing.
Implementation
// core/collaboration/OperationalTransform.ts
export interface Operation {
type: 'insert' | 'delete';
position: number;
content?: string;
length?: number;
userId: string;
timestamp: number;
}
export class OperationalTransform {
static transform(op1: Operation, op2: Operation): Operation {
// If both are inserts
if (op1.type === 'insert' && op2.type === 'insert') {
if (op1.position < op2.position) {
return op2;
} else if (op1.position > op2.position) {
return {
...op2,
position: op2.position + (op1.content?.length || 0)
};
} else {
// Same position, use timestamp to decide
return op1.timestamp < op2.timestamp
? op2
: { ...op2, position: op2.position + (op1.content?.length || 0) };
}
}
// If both are deletes
if (op1.type === 'delete' && op2.type === 'delete') {
if (op1.position < op2.position) {
return {
...op2,
position: op2.position - (op1.length || 0)
};
}
return op2;
}
// Insert vs Delete
if (op1.type === 'insert' && op2.type === 'delete') {
if (op1.position <= op2.position) {
return {
...op2,
position: op2.position + (op1.content?.length || 0)
};
}
return op2;
}
// Delete vs Insert
if (op1.type === 'delete' && op2.type === 'insert') {
if (op1.position < op2.position) {
return {
...op2,
position: op2.position - (op1.length || 0)
};
}
return op2;
}
return op2;
}
static apply(content: string, operation: Operation): string {
if (operation.type === 'insert') {
return (
content.slice(0, operation.position) +
operation.content +
content.slice(operation.position)
);
}
if (operation.type === 'delete') {
return (
content.slice(0, operation.position) +
content.slice(operation.position + (operation.length || 0))
);
}
return content;
}
}
// features/editor/hooks/useCollaborativeEditor.ts
export function useCollaborativeEditor(documentId: string) {
const [content, setContent] = useState('');
const [cursors, setCursors] = useState<Map<string, number>>(new Map());
const pendingOperations = useRef<Operation[]>([]);
const ws = useWebSocketManager();
const { user } = useAuth();
// Load initial content
useEffect(() => {
loadDocument(documentId).then(doc => setContent(doc.content));
}, [documentId]);
// Subscribe to remote operations
useEffect(() => {
const unsubscribe = ws.subscribe('operation', (op: Operation) => {
if (op.userId === user?.id) return;
// Transform pending operations
pendingOperations.current = pendingOperations.current.map(
pendingOp => OperationalTransform.transform(op, pendingOp)
);
// Apply operation
setContent(content =>
OperationalTransform.apply(content, op)
);
});
return unsubscribe;
}, [ws, user]);
// Subscribe to cursor updates
useEffect(() => {
const unsubscribe = ws.subscribe('cursor', ({ userId, position }) => {
setCursors(prev => new Map(prev).set(userId, position));
});
return unsubscribe;
}, [ws]);
const handleChange = useCallback((newContent: string, position: number) => {
const operation: Operation = {
type: newContent.length > content.length ? 'insert' : 'delete',
position,
content: newContent.length > content.length
? newContent.slice(position, position + (newContent.length - content.length))
: undefined,
length: newContent.length < content.length
? content.length - newContent.length
: undefined,
userId: user!.id,
timestamp: Date.now()
};
// Apply locally
setContent(newContent);
// Send to server
ws.send('operation', operation);
pendingOperations.current.push(operation);
// Acknowledge from server
ws.subscribe('operation:ack', (ackOp: Operation) => {
if (ackOp.timestamp === operation.timestamp) {
pendingOperations.current = pendingOperations.current.filter(
op => op.timestamp !== operation.timestamp
);
}
});
}, [content, ws, user]);
const handleCursorMove = useCallback((position: number) => {
ws.send('cursor', { position });
}, [ws]);
return {
content,
cursors,
handleChange,
handleCursorMove
};
}---
5. Micro-Frontend Architecture
Module federation and independent deployment of frontend modules.
Webpack Module Federation Configuration
// host/webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
products: 'products@http://localhost:3001/remoteEntry.js',
cart: 'cart@http://localhost:3002/remoteEntry.js',
checkout: 'checkout@http://localhost:3003/remoteEntry.js'
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' }
}
})
]
};
// products/webpack.config.js
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'products',
filename: 'remoteEntry.js',
exposes: {
'./ProductList': './src/components/ProductList',
'./ProductDetail': './src/components/ProductDetail'
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true }
}
})
]
};
// host/src/App.tsx
import { lazy, Suspense } from 'react';
const ProductList = lazy(() => import('products/ProductList'));
const Cart = lazy(() => import('cart/Cart'));
const Checkout = lazy(() => import('checkout/Checkout'));
function App() {
return (
<BrowserRouter>
<Header />
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/products" element={<ProductList />} />
<Route path="/cart" element={<Cart />} />
<Route path="/checkout" element={<Checkout />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
// Shared event bus for micro-frontend communication
class EventBus {
private events = new Map<string, Set<(data: any) => void>>();
emit(event: string, data?: any) {
this.events.get(event)?.forEach(handler => handler(data));
}
on(event: string, handler: (data: any) => void) {
if (!this.events.has(event)) {
this.events.set(event, new Set());
}
this.events.get(event)!.add(handler);
return () => this.events.get(event)?.delete(handler);
}
}
export const eventBus = new EventBus();
// Usage in micro-frontends
// In products module
eventBus.emit('product:added-to-cart', { productId: '123', quantity: 1 });
// In cart module
eventBus.on('product:added-to-cart', ({ productId, quantity }) => {
addItemToCart(productId, quantity);
});---
6. Offline-First PWA
Service workers, IndexedDB, and background sync.
Implementation
// core/offline/ServiceWorkerManager.ts
export class ServiceWorkerManager {
async register() {
if ('serviceWorker' in navigator) {
try {
const registration = await navigator.serviceWorker.register('/sw.js');
console.log('Service Worker registered:', registration);
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker?.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// New version available
this.notifyUpdate();
}
});
});
} catch (error) {
console.error('Service Worker registration failed:', error);
}
}
}
private notifyUpdate() {
if (confirm('New version available. Update now?')) {
window.location.reload();
}
}
async unregister() {
const registrations = await navigator.serviceWorker.getRegistrations();
for (const registration of registrations) {
await registration.unregister();
}
}
}
// public/sw.js
const CACHE_NAME = 'app-v1';
const RUNTIME_CACHE = 'runtime';
const STATIC_ASSETS = [
'/',
'/index.html',
'/static/css/main.css',
'/static/js/main.js'
];
// Install - cache static assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(STATIC_ASSETS);
})
);
self.skipWaiting();
});
// Activate - clean old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames
.filter((name) => name !== CACHE_NAME && name !== RUNTIME_CACHE)
.map((name) => caches.delete(name))
);
})
);
self.clients.claim();
});
// Fetch - network first, fall back to cache
self.addEventListener('fetch', (event) => {
const { request } = event;
// API requests - network first
if (request.url.includes('/api/')) {
event.respondWith(
fetch(request)
.then((response) => {
const clone = response.clone();
caches.open(RUNTIME_CACHE).then((cache) => {
cache.put(request, clone);
});
return response;
})
.catch(() => {
return caches.match(request);
})
);
return;
}
// Static assets - cache first
event.respondWith(
caches.match(request).then((cached) => {
return cached || fetch(request);
})
);
});
// Background sync
self.addEventListener('sync', (event) => {
if (event.tag === 'sync-posts') {
event.waitUntil(syncPosts());
}
});
async function syncPosts() {
const db = await openDB();
const posts = await db.getAll('pending-posts');
for (const post of posts) {
try {
await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(post)
});
await db.delete('pending-posts', post.id);
} catch (error) {
console.error('Sync failed:', error);
}
}
}
// core/offline/useOfflineSync.ts
export function useOfflineSync() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
const createPost = useMutation({
mutationFn: async (content: string) => {
if (isOnline) {
return postService.create(content);
} else {
// Save to IndexedDB
const db = await openDB();
const post = {
id: crypto.randomUUID(),
content,
createdAt: new Date(),
synced: false
};
await db.add('pending-posts', post);
// Register background sync
if ('serviceWorker' in navigator && 'sync' in ServiceWorkerRegistration.prototype) {
const registration = await navigator.serviceWorker.ready;
await registration.sync.register('sync-posts');
}
return post;
}
}
});
return { isOnline, createPost };
}---
7. Admin Dashboard with RBAC
Complete role-based access control system.
Implementation
// core/rbac/permissions.ts
export const PERMISSIONS = {
// Users
'users:read': 'View users',
'users:create': 'Create users',
'users:update': 'Update users',
'users:delete': 'Delete users',
// Posts
'posts:read': 'View posts',
'posts:create': 'Create posts',
'posts:update': 'Update posts',
'posts:delete': 'Delete posts',
'posts:publish': 'Publish posts',
// Settings
'settings:read': 'View settings',
'settings:update': 'Update settings'
} as const;
export type Permission = keyof typeof PERMISSIONS;
export const ROLES = {
admin: Object.keys(PERMISSIONS) as Permission[],
editor: [
'users:read',
'posts:read',
'posts:create',
'posts:update',
'posts:publish'
] as Permission[],
author: [
'posts:read',
'posts:create',
'posts:update'
] as Permission[],
viewer: [
'users:read',
'posts:read'
] as Permission[]
};
export type Role = keyof typeof ROLES;
// core/rbac/useAuthorization.ts
export function useAuthorization() {
const { user } = useAuth();
const hasPermission = useCallback(
(permission: Permission): boolean => {
if (!user) return false;
const userPermissions = user.roles.flatMap(role => ROLES[role] || []);
return userPermissions.includes(permission);
},
[user]
);
const hasAnyPermission = useCallback(
(permissions: Permission[]): boolean => {
return permissions.some(hasPermission);
},
[hasPermission]
);
const hasAllPermissions = useCallback(
(permissions: Permission[]): boolean => {
return permissions.every(hasPermission);
},
[hasPermission]
);
const hasRole = useCallback(
(role: Role): boolean => {
return user?.roles.includes(role) ?? false;
},
[user]
);
return {
hasPermission,
hasAnyPermission,
hasAllPermissions,
hasRole
};
}
// shared/components/Can.tsx
export function Can({
permission,
permissions,
requireAll = false,
fallback,
children
}: {
permission?: Permission;
permissions?: Permission[];
requireAll?: boolean;
fallback?: React.ReactNode;
children: React.ReactNode;
}) {
const { hasPermission, hasAnyPermission, hasAllPermissions } = useAuthorization();
let authorized = false;
if (permission) {
authorized = hasPermission(permission);
} else if (permissions) {
authorized = requireAll
? hasAllPermissions(permissions)
: hasAnyPermission(permissions);
}
if (!authorized) {
return <>{fallback || null}</>;
}
return <>{children}</>;
}
// Usage
function UserManagement() {
return (
<div>
<h1>User Management</h1>
<Can permission="users:create">
<Button>Create User</Button>
</Can>
<Can permission="users:read">
<UserList />
</Can>
<Can
permissions={['users:update', 'users:delete']}
fallback={<p>You don't have permission to manage users.</p>}
>
<UserActions />
</Can>
</div>
);
}---
8. Form Builder Application
Dynamic form generation with validation.
Implementation
// features/form-builder/types.ts
export interface FormField {
id: string;
type: 'text' | 'email' | 'number' | 'select' | 'checkbox' | 'radio' | 'textarea';
label: string;
placeholder?: string;
required?: boolean;
validation?: ValidationRule[];
options?: { value: string; label: string }[];
defaultValue?: any;
}
export interface ValidationRule {
type: 'required' | 'min' | 'max' | 'pattern' | 'custom';
value?: any;
message: string;
}
export interface FormSchema {
id: string;
title: string;
description?: string;
fields: FormField[];
submitButton: string;
}
// features/form-builder/FormRenderer.tsx
export function FormRenderer({ schema }: { schema: FormSchema }) {
const {
register,
handleSubmit,
formState: { errors },
watch
} = useForm();
const onSubmit = (data: any) => {
console.log('Form submitted:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h2>{schema.title}</h2>
{schema.description && <p>{schema.description}</p>}
{schema.fields.map(field => (
<div key={field.id} className="form-field">
<label htmlFor={field.id}>
{field.label}
{field.required && <span className="required">*</span>}
</label>
{field.type === 'text' || field.type === 'email' || field.type === 'number' ? (
<input
id={field.id}
type={field.type}
placeholder={field.placeholder}
{...register(field.id, getValidationRules(field))}
/>
) : field.type === 'textarea' ? (
<textarea
id={field.id}
placeholder={field.placeholder}
{...register(field.id, getValidationRules(field))}
/>
) : field.type === 'select' ? (
<select id={field.id} {...register(field.id, getValidationRules(field))}>
<option value="">Select...</option>
{field.options?.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
) : field.type === 'checkbox' ? (
<input
id={field.id}
type="checkbox"
{...register(field.id, getValidationRules(field))}
/>
) : null}
{errors[field.id] && (
<span className="error">{errors[field.id]?.message as string}</span>
)}
</div>
))}
<button type="submit">{schema.submitButton}</button>
</form>
);
}
function getValidationRules(field: FormField) {
const rules: any = {};
field.validation?.forEach(rule => {
if (rule.type === 'required') {
rules.required = rule.message;
} else if (rule.type === 'min') {
rules.min = { value: rule.value, message: rule.message };
} else if (rule.type === 'max') {
rules.max = { value: rule.value, message: rule.message };
} else if (rule.type === 'pattern') {
rules.pattern = { value: new RegExp(rule.value), message: rule.message };
}
});
return rules;
}---
9. Data Visualization Platform
Large dataset handling with virtualization.
Implementation
// features/visualization/hooks/useVirtualization.ts
export function useVirtualization<T>({
items,
itemHeight,
containerHeight,
overscan = 3
}: {
items: T[];
itemHeight: number;
containerHeight: number;
overscan?: number;
}) {
const [scrollTop, setScrollTop] = useState(0);
const visibleStart = Math.floor(scrollTop / itemHeight);
const visibleEnd = Math.ceil((scrollTop + containerHeight) / itemHeight);
const start = Math.max(0, visibleStart - overscan);
const end = Math.min(items.length, visibleEnd + overscan);
const visibleItems = items.slice(start, end);
const totalHeight = items.length * itemHeight;
const offsetY = start * itemHeight;
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
setScrollTop(e.currentTarget.scrollTop);
};
return {
visibleItems,
totalHeight,
offsetY,
handleScroll,
start,
end
};
}
// Usage
function LargeList({ items }: { items: DataPoint[] }) {
const containerRef = useRef<HTMLDivElement>(null);
const [containerHeight, setContainerHeight] = useState(600);
const {
visibleItems,
totalHeight,
offsetY,
handleScroll
} = useVirtualization({
items,
itemHeight: 50,
containerHeight
});
useEffect(() => {
if (containerRef.current) {
setContainerHeight(containerRef.current.clientHeight);
}
}, []);
return (
<div
ref={containerRef}
style={{ height: '600px', overflow: 'auto' }}
onScroll={handleScroll}
>
<div style={{ height: totalHeight, position: 'relative' }}>
<div style={{ transform: `translateY(${offsetY}px)` }}>
{visibleItems.map((item, index) => (
<DataRow key={start + index} data={item} />
))}
</div>
</div>
</div>
);
}---
10. Plugin-Based Architecture
Extensible plugin system.
Implementation
// core/plugins/PluginManager.ts
export interface Plugin {
name: string;
version: string;
initialize: (context: PluginContext) => void | Promise<void>;
destroy?: () => void | Promise<void>;
}
export interface PluginContext {
registerComponent: (name: string, component: React.ComponentType) => void;
registerRoute: (path: string, component: React.ComponentType) => void;
registerMenuItem: (item: MenuItem) => void;
registerHook: (name: string, callback: Function) => void;
getAPI: () => API;
}
export class PluginManager {
private plugins: Map<string, Plugin> = new Map();
private components: Map<string, React.ComponentType> = new Map();
private routes: Array<{ path: string; component: React.ComponentType }> = [];
private menuItems: MenuItem[] = [];
private hooks: Map<string, Set<Function>> = new Map();
async loadPlugin(plugin: Plugin) {
const context: PluginContext = {
registerComponent: (name, component) => {
this.components.set(name, component);
},
registerRoute: (path, component) => {
this.routes.push({ path, component });
},
registerMenuItem: (item) => {
this.menuItems.push(item);
},
registerHook: (name, callback) => {
if (!this.hooks.has(name)) {
this.hooks.set(name, new Set());
}
this.hooks.get(name)!.add(callback);
},
getAPI: () => this.getAPI()
};
await plugin.initialize(context);
this.plugins.set(plugin.name, plugin);
}
async unloadPlugin(name: string) {
const plugin = this.plugins.get(name);
if (plugin?.destroy) {
await plugin.destroy();
}
this.plugins.delete(name);
}
getComponent(name: string) {
return this.components.get(name);
}
getRoutes() {
return this.routes;
}
getMenuItems() {
return this.menuItems;
}
executeHook(name: string, ...args: any[]) {
const callbacks = this.hooks.get(name);
if (callbacks) {
callbacks.forEach(callback => callback(...args));
}
}
private getAPI() {
// Return API for plugins
return {
// API methods
};
}
}
// Example plugin
export const AnalyticsPlugin: Plugin = {
name: 'analytics',
version: '1.0.0',
initialize(context) {
// Register components
context.registerComponent('AnalyticsDashboard', AnalyticsDashboard);
// Register routes
context.registerRoute('/analytics', AnalyticsDashboard);
// Register menu items
context.registerMenuItem({
label: 'Analytics',
path: '/analytics',
icon: 'chart'
});
// Register hooks
context.registerHook('page:view', (page: string) => {
console.log('Page viewed:', page);
});
},
destroy() {
console.log('Analytics plugin destroyed');
}
};---
11. State Machine Architecture
Using XState for predictable state transitions and complex workflows.
Implementation
// features/checkout/machines/checkoutMachine.ts
import { createMachine, assign } from 'xstate';
interface CheckoutContext {
cart: CartItem[];
shippingAddress: Address | null;
paymentMethod: PaymentMethod | null;
error: string | null;
}
type CheckoutEvent =
| { type: 'NEXT' }
| { type: 'BACK' }
| { type: 'SET_SHIPPING'; address: Address }
| { type: 'SET_PAYMENT'; payment: PaymentMethod }
| { type: 'SUBMIT' }
| { type: 'RETRY' };
export const checkoutMachine = createMachine<CheckoutContext, CheckoutEvent>({
id: 'checkout',
initial: 'cart',
context: {
cart: [],
shippingAddress: null,
paymentMethod: null,
error: null
},
states: {
cart: {
on: {
NEXT: {
target: 'shipping',
cond: (context) => context.cart.length > 0
}
}
},
shipping: {
on: {
BACK: 'cart',
SET_SHIPPING: {
actions: assign({
shippingAddress: (_, event) => event.address
})
},
NEXT: {
target: 'payment',
cond: (context) => context.shippingAddress !== null
}
}
},
payment: {
on: {
BACK: 'shipping',
SET_PAYMENT: {
actions: assign({
paymentMethod: (_, event) => event.payment
})
},
SUBMIT: {
target: 'processing',
cond: (context) => context.paymentMethod !== null
}
}
},
processing: {
invoke: {
src: 'processOrder',
onDone: 'success',
onError: {
target: 'error',
actions: assign({
error: (_, event) => event.data.message
})
}
}
},
success: {
type: 'final'
},
error: {
on: {
RETRY: 'payment',
BACK: 'payment'
}
}
}
}, {
services: {
processOrder: async (context) => {
const response = await fetch('/api/orders', {
method: 'POST',
body: JSON.stringify({
cart: context.cart,
shipping: context.shippingAddress,
payment: context.paymentMethod
})
});
if (!response.ok) {
throw new Error('Payment failed');
}
return response.json();
}
}
});
// Usage in component
function CheckoutFlow() {
const [state, send] = useMachine(checkoutMachine);
return (
<div>
{state.matches('cart') && (
<CartStep onNext={() => send('NEXT')} />
)}
{state.matches('shipping') && (
<ShippingStep
onBack={() => send('BACK')}
onNext={(address) => {
send({ type: 'SET_SHIPPING', address });
send('NEXT');
}}
/>
)}
{state.matches('payment') && (
<PaymentStep
onBack={() => send('BACK')}
onSubmit={(payment) => {
send({ type: 'SET_PAYMENT', payment });
send('SUBMIT');
}}
/>
)}
{state.matches('processing') && <ProcessingSpinner />}
{state.matches('success') && <OrderSuccess />}
{state.matches('error') && (
<ErrorMessage
message={state.context.error}
onRetry={() => send('RETRY')}
/>
)}
</div>
);
}---
12. Event-Driven Architecture
Decoupled components communicating through events.
Implementation
// core/events/EventEmitter.ts
export class EventEmitter<T extends Record<string, any>> {
private events = new Map<keyof T, Set<(data: any) => void>>();
on<K extends keyof T>(event: K, handler: (data: T[K]) => void): () => void {
if (!this.events.has(event)) {
this.events.set(event, new Set());
}
this.events.get(event)!.add(handler);
return () => {
this.events.get(event)?.delete(handler);
};
}
emit<K extends keyof T>(event: K, data: T[K]): void {
this.events.get(event)?.forEach(handler => handler(data));
}
once<K extends keyof T>(event: K, handler: (data: T[K]) => void): void {
const unsubscribe = this.on(event, (data) => {
handler(data);
unsubscribe();
});
}
}
// Application events
interface AppEvents {
'user:login': { user: User };
'user:logout': void;
'notification:show': { message: string; type: 'success' | 'error' };
'cart:item-added': { productId: string; quantity: number };
'order:completed': { orderId: string; total: number };
}
export const appEvents = new EventEmitter<AppEvents>();
// Usage in features
function ProductCard({ product }: { product: Product }) {
const handleAddToCart = () => {
// Emit event instead of direct coupling
appEvents.emit('cart:item-added', {
productId: product.id,
quantity: 1
});
appEvents.emit('notification:show', {
message: 'Product added to cart',
type: 'success'
});
};
return (
<div>
<h3>{product.name}</h3>
<button onClick={handleAddToCart}>Add to Cart</button>
</div>
);
}
// Cart component listens to events
function Cart() {
const [items, setItems] = useState<CartItem[]>([]);
useEffect(() => {
return appEvents.on('cart:item-added', ({ productId, quantity }) => {
// Update cart
setItems(prev => [...prev, { productId, quantity }]);
});
}, []);
return <div>{/* Cart UI */}</div>;
}
// Notification component listens to events
function NotificationManager() {
const [notifications, setNotifications] = useState<Notification[]>([]);
useEffect(() => {
return appEvents.on('notification:show', ({ message, type }) => {
const id = crypto.randomUUID();
setNotifications(prev => [...prev, { id, message, type }]);
setTimeout(() => {
setNotifications(prev => prev.filter(n => n.id !== id));
}, 3000);
});
}, []);
return (
<div className="notifications">
{notifications.map(n => (
<div key={n.id} className={`notification ${n.type}`}>
{n.message}
</div>
))}
</div>
);
}---
13. Layered Architecture Pattern
Clean separation of concerns across application layers.
Implementation
// Domain Layer - Business entities and logic
export class User {
constructor(
public readonly id: string,
public email: string,
public name: string,
private _role: UserRole
) {}
get role(): UserRole {
return this._role;
}
canEdit(resource: Resource): boolean {
return this._role === 'admin' || resource.authorId === this.id;
}
updateProfile(name: string, email: string): void {
if (!this.isValidEmail(email)) {
throw new Error('Invalid email');
}
this.name = name;
this.email = email;
}
private isValidEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
}
// Infrastructure Layer - Data access
export interface IUserRepository {
findById(id: string): Promise<User>;
save(user: User): Promise<void>;
delete(id: string): Promise<void>;
}
export class HttpUserRepository implements IUserRepository {
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
});
}
async delete(id: string): Promise<void> {
await this.apiClient.delete(`/users/${id}`);
}
}
// Application Layer - Use cases
export class UpdateUserProfileUseCase {
constructor(private userRepository: IUserRepository) {}
async execute(userId: string, name: string, email: string): Promise<User> {
const user = await this.userRepository.findById(userId);
user.updateProfile(name, email);
await this.userRepository.save(user);
return user;
}
}
// Presentation Layer - React components
function UserProfileForm({ userId }: { userId: string }) {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const useCase = useUpdateUserProfileUseCase();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await useCase.execute(userId, name, email);
toast.success('Profile updated');
} catch (error) {
toast.error(error.message);
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<button type="submit">Update Profile</button>
</form>
);
}---
14. Repository Pattern Implementation
Abstract data access layer for flexibility and testability.
Implementation
// Domain interfaces
export interface IRepository<T> {
findAll(): Promise<T[]>;
findById(id: string): Promise<T | null>;
create(entity: Omit<T, 'id'>): Promise<T>;
update(id: string, entity: Partial<T>): Promise<T>;
delete(id: string): Promise<void>;
}
// Concrete repository implementations
export class HttpRepository<T extends { id: string }> implements IRepository<T> {
constructor(
private endpoint: string,
private apiClient: ApiClient
) {}
async findAll(): Promise<T[]> {
const response = await this.apiClient.get(this.endpoint);
return response.data;
}
async findById(id: string): Promise<T | null> {
try {
const response = await this.apiClient.get(`${this.endpoint}/${id}`);
return response.data;
} catch (error) {
if (error.status === 404) return null;
throw error;
}
}
async create(entity: Omit<T, 'id'>): Promise<T> {
const response = await this.apiClient.post(this.endpoint, entity);
return response.data;
}
async update(id: string, entity: Partial<T>): Promise<T> {
const response = await this.apiClient.patch(`${this.endpoint}/${id}`, entity);
return response.data;
}
async delete(id: string): Promise<void> {
await this.apiClient.delete(`${this.endpoint}/${id}`);
}
}
// Cached repository decorator
export class CachedRepository<T extends { id: string }> implements IRepository<T> {
private cache = new Map<string, { data: T; timestamp: number }>();
private cacheDuration = 5 * 60 * 1000; // 5 minutes
constructor(private repository: IRepository<T>) {}
async findAll(): Promise<T[]> {
return this.repository.findAll();
}
async findById(id: string): Promise<T | null> {
const cached = this.cache.get(id);
const now = Date.now();
if (cached && now - cached.timestamp < this.cacheDuration) {
return cached.data;
}
const entity = await this.repository.findById(id);
if (entity) {
this.cache.set(id, { data: entity, timestamp: now });
}
return entity;
}
async create(entity: Omit<T, 'id'>): Promise<T> {
const created = await this.repository.create(entity);
this.cache.set(created.id, { data: created, timestamp: Date.now() });
return created;
}
async update(id: string, entity: Partial<T>): Promise<T> {
const updated = await this.repository.update(id, entity);
this.cache.set(id, { data: updated, timestamp: Date.now() });
return updated;
}
async delete(id: string): Promise<void> {
await this.repository.delete(id);
this.cache.delete(id);
}
}
// Usage
const productRepository = new CachedRepository(
new HttpRepository<Product>('/api/products', apiClient)
);
function useProducts() {
const repository = useProductRepository();
return useQuery({
queryKey: ['products'],
queryFn: () => repository.findAll()
});
}---
15. Feature Flag System
Runtime feature toggles and A/B testing.
Implementation
// core/features/FeatureFlagManager.ts
export interface FeatureFlag {
key: string;
enabled: boolean;
rolloutPercentage?: number;
variants?: Record<string, any>;
rules?: FeatureRule[];
}
export interface FeatureRule {
attribute: string;
operator: 'equals' | 'contains' | 'gt' | 'lt';
value: any;
}
export class FeatureFlagManager {
private flags = new Map<string, FeatureFlag>();
private userAttributes: Record<string, any> = {};
setUserAttributes(attributes: Record<string, any>) {
this.userAttributes = attributes;
}
registerFlag(flag: FeatureFlag) {
this.flags.set(flag.key, flag);
}
isEnabled(key: string): boolean {
const flag = this.flags.get(key);
if (!flag) return false;
// Check if globally enabled
if (!flag.enabled) return false;
// Check rules
if (flag.rules && !this.evaluateRules(flag.rules)) {
return false;
}
// Check rollout percentage
if (flag.rolloutPercentage !== undefined) {
const hash = this.hashString(this.userAttributes.userId + key);
const bucket = hash % 100;
return bucket < flag.rolloutPercentage;
}
return true;
}
getVariant(key: string): string | null {
const flag = this.flags.get(key);
if (!flag || !this.isEnabled(key)) return null;
if (flag.variants) {
const variantKeys = Object.keys(flag.variants);
const hash = this.hashString(this.userAttributes.userId + key);
const index = hash % variantKeys.length;
return variantKeys[index];
}
return null;
}
private evaluateRules(rules: FeatureRule[]): boolean {
return rules.every(rule => {
const value = this.userAttributes[rule.attribute];
switch (rule.operator) {
case 'equals':
return value === rule.value;
case 'contains':
return Array.isArray(value) && value.includes(rule.value);
case 'gt':
return value > rule.value;
case 'lt':
return value < rule.value;
default:
return false;
}
});
}
private hashString(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash);
}
}
// React integration
const FeatureFlagContext = createContext<FeatureFlagManager>(null!);
export function FeatureFlagProvider({ children }: { children: React.ReactNode }) {
const [manager] = useState(() => new FeatureFlagManager());
const { user } = useAuth();
useEffect(() => {
if (user) {
manager.setUserAttributes({
userId: user.id,
email: user.email,
plan: user.plan,
signupDate: user.createdAt
});
}
}, [user, manager]);
useEffect(() => {
// Load feature flags from server
fetch('/api/feature-flags')
.then(r => r.json())
.then(flags => {
flags.forEach((flag: FeatureFlag) => manager.registerFlag(flag));
});
}, [manager]);
return (
<FeatureFlagContext.Provider value={manager}>
{children}
</FeatureFlagContext.Provider>
);
}
export function useFeatureFlag(key: string) {
const manager = useContext(FeatureFlagContext);
const [enabled, setEnabled] = useState(manager.isEnabled(key));
useEffect(() => {
setEnabled(manager.isEnabled(key));
}, [manager, key]);
return enabled;
}
export function useFeatureVariant(key: string) {
const manager = useContext(FeatureFlagContext);
return manager.getVariant(key);
}
// Usage
function Dashboard() {
const newDashboard = useFeatureFlag('new-dashboard');
const variant = useFeatureVariant('dashboard-layout');
if (newDashboard) {
return variant === 'compact' ? <CompactDashboard /> : <ExpandedDashboard />;
}
return <LegacyDashboard />;
}---
16. Advanced Caching Strategy
Multi-level caching with invalidation and persistence.
Implementation
// core/cache/CacheManager.ts
export interface CacheEntry<T> {
data: T;
timestamp: number;
expiresAt: number;
tags: string[];
}
export class CacheManager {
private memoryCache = new Map<string, CacheEntry<any>>();
private persistentCache: IDBDatabase | null = null;
async initialize() {
this.persistentCache = await openDB('app-cache', 1, {
upgrade(db) {
db.createObjectStore('cache');
}
});
}
async get<T>(key: string): Promise<T | null> {
// Check memory cache first
const memoryCached = this.memoryCache.get(key);
if (memoryCached && memoryCached.expiresAt > Date.now()) {
return memoryCached.data;
}
// Check persistent cache
if (this.persistentCache) {
const tx = this.persistentCache.transaction('cache', 'readonly');
const store = tx.objectStore('cache');
const cached = await store.get(key);
if (cached && cached.expiresAt > Date.now()) {
// Promote to memory cache
this.memoryCache.set(key, cached);
return cached.data;
}
}
return null;
}
async set<T>(
key: string,
data: T,
ttl: number = 5 * 60 * 1000,
tags: string[] = []
): Promise<void> {
const entry: CacheEntry<T> = {
data,
timestamp: Date.now(),
expiresAt: Date.now() + ttl,
tags
};
// Set in memory cache
this.memoryCache.set(key, entry);
// Set in persistent cache
if (this.persistentCache) {
const tx = this.persistentCache.transaction('cache', 'readwrite');
const store = tx.objectStore('cache');
await store.put(entry, key);
}
}
async invalidate(key: string): Promise<void> {
this.memoryCache.delete(key);
if (this.persistentCache) {
const tx = this.persistentCache.transaction('cache', 'readwrite');
const store = tx.objectStore('cache');
await store.delete(key);
}
}
async invalidateByTag(tag: string): Promise<void> {
// Invalidate memory cache
for (const [key, entry] of this.memoryCache.entries()) {
if (entry.tags.includes(tag)) {
this.memoryCache.delete(key);
}
}
// Invalidate persistent cache
if (this.persistentCache) {
const tx = this.persistentCache.transaction('cache', 'readwrite');
const store = tx.objectStore('cache');
const keys = await store.getAllKeys();
for (const key of keys) {
const entry = await store.get(key);
if (entry?.tags.includes(tag)) {
await store.delete(key);
}
}
}
}
async clear(): Promise<void> {
this.memoryCache.clear();
if (this.persistentCache) {
const tx = this.persistentCache.transaction('cache', 'readwrite');
const store = tx.objectStore('cache');
await store.clear();
}
}
}
// React Query integration with cache
export function useCachedQuery<T>(
queryKey: string[],
queryFn: () => Promise<T>,
options?: {
ttl?: number;
tags?: string[];
}
) {
const cache = useCacheManager();
return useQuery({
queryKey,
queryFn: async () => {
const cacheKey = queryKey.join(':');
const cached = await cache.get<T>(cacheKey);
if (cached) {
return cached;
}
const data = await queryFn();
await cache.set(cacheKey, data, options?.ttl, options?.tags);
return data;
}
});
}
// Usage
function ProductList() {
const { data: products } = useCachedQuery(
['products'],
() => fetchProducts(),
{
ttl: 10 * 60 * 1000, // 10 minutes
tags: ['products']
}
);
return <div>{/* Render products */}</div>;
}
// Invalidate cache when mutation succeeds
function useCreateProduct() {
const cache = useCacheManager();
return useMutation({
mutationFn: createProduct,
onSuccess: () => {
cache.invalidateByTag('products');
}
});
}---
17. Scalable Testing Architecture
Comprehensive testing utilities, factories, and patterns.
Implementation
// test/utils/renderWithProviders.tsx
export function renderWithProviders(
ui: React.ReactElement,
options?: {
initialState?: Partial<AppState>;
user?: User;
featureFlags?: Record<string, boolean>;
}
) {
const queryClient = new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false }
}
});
const mockStore = createMockStore(options?.initialState);
function Wrapper({ children }: { children: React.ReactNode }) {
return (
<Provider store={mockStore}>
<QueryClientProvider client={queryClient}>
<MemoryRouter>
<AuthProvider initialUser={options?.user}>
<FeatureFlagProvider initialFlags={options?.featureFlags}>
{children}
</FeatureFlagProvider>
</AuthProvider>
</MemoryRouter>
</QueryClientProvider>
</Provider>
);
}
return {
...render(ui, { wrapper: Wrapper }),
queryClient,
store: mockStore
};
}
// test/factories/userFactory.ts
export class UserFactory {
private defaults: Partial<User> = {
id: crypto.randomUUID(),
email: 'test@example.com',
name: 'Test User',
role: 'user',
createdAt: new Date()
};
build(overrides?: Partial<User>): User {
return {
...this.defaults,
...overrides
} as User;
}
buildMany(count: number, overrides?: Partial<User>): User[] {
return Array.from({ length: count }, () => this.build(overrides));
}
admin(overrides?: Partial<User>): User {
return this.build({ ...overrides, role: 'admin' });
}
withPosts(postCount: number): User & { posts: Post[] } {
const user = this.build();
const posts = new PostFactory().buildMany(postCount, { authorId: user.id });
return { ...user, posts };
}
}
// test/mocks/apiMocks.ts
export function setupApiMocks() {
const server = setupServer();
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
return {
mockGetProducts: (products: Product[]) => {
server.use(
rest.get('/api/products', (req, res, ctx) => {
return res(ctx.json(products));
})
);
},
mockCreateProduct: (product: Product) => {
server.use(
rest.post('/api/products', (req, res, ctx) => {
return res(ctx.json(product));
})
);
},
mockError: (endpoint: string, status: number, message: string) => {
server.use(
rest.all(endpoint, (req, res, ctx) => {
return res(ctx.status(status), ctx.json({ message }));
})
);
}
};
}
// test/custom-matchers.ts
expect.extend({
toHaveBeenCalledWithUser(received: jest.Mock, user: User) {
const pass = received.mock.calls.some(call =>
call.some(arg => arg?.id === user.id)
);
return {
pass,
message: () =>
pass
? `Expected mock not to have been called with user ${user.id}`
: `Expected mock to have been called with user ${user.id}`
};
},
toBeInLoadingState(received: HTMLElement) {
const hasSpinner = received.querySelector('[data-testid="loading"]');
const hasLoadingText = received.textContent?.includes('Loading');
return {
pass: Boolean(hasSpinner || hasLoadingText),
message: () =>
hasSpinner || hasLoadingText
? 'Expected element not to be in loading state'
: 'Expected element to be in loading state'
};
}
});
// Example test
describe('ProductList', () => {
const api = setupApiMocks();
const factory = new UserFactory();
it('should display products', async () => {
const products = new ProductFactory().buildMany(3);
api.mockGetProducts(products);
const { getByText } = renderWithProviders(<ProductList />);
await waitFor(() => {
products.forEach(product => {
expect(getByText(product.name)).toBeInTheDocument();
});
});
});
it('should handle errors', async () => {
api.mockError('/api/products', 500, 'Server error');
const { getByText } = renderWithProviders(<ProductList />);
await waitFor(() => {
expect(getByText(/error/i)).toBeInTheDocument();
});
});
});---
This completes all 17 comprehensive frontend architecture examples, demonstrating production-ready patterns for modern web applications.
Frontend Architecture Skill
A comprehensive guide to building scalable, maintainable frontend applications with modern architectural patterns.
Overview
This skill covers essential frontend architecture concepts including component design, state management, design patterns, module systems, build optimization, and testing strategies. It provides practical examples and best practices for structuring large-scale applications.
What You'll Learn
Core Architecture Concepts
- Component-Based Architecture - Design principles for building modular, reusable components
- Separation of Concerns - Layered architecture for maintainability
- Design Patterns - MVC, MVVM, Flux, Observer, Factory, and Module patterns
- State Management - Strategies for managing application state at scale
- Module Systems - ES modules, code splitting, and lazy loading techniques
Build and Performance
- Build Tools - Webpack and Vite configuration for optimal builds
- Code Splitting - Strategies for reducing initial bundle size
- Tree Shaking - Eliminating dead code from production bundles
- Caching - Browser caching, service workers, and HTTP caching strategies
- Performance Optimization - Techniques for fast load times and smooth runtime
Testing and Quality
- Testing Architecture - Unit, integration, and E2E testing strategies
- Test Patterns - Utilities, mocks, and custom matchers
- Testing Pyramid - Balancing different types of tests
Scalability
- Folder Structure - Feature-based organization for growing codebases
- Naming Conventions - Consistent patterns for files, components, and functions
- Dependency Management - Dependency injection and module boundaries
Key Patterns
Component Patterns
Container vs Presentational Components
Separate data fetching logic from UI rendering:
// Presentational - Pure UI
function UserCard({ user, onEdit }: UserCardProps) {
return (
<div className="user-card">
<h3>{user.name}</h3>
<button onClick={onEdit}>Edit</button>
</div>
);
}
// Container - Data and logic
function UserCardContainer({ userId }: { userId: string }) {
const { data: user } = useQuery(['user', userId], fetchUser);
const navigate = useNavigate();
return <UserCard user={user} onEdit={() => navigate(`/edit/${userId}`)} />;
}Composition
Build complex components from simple ones:
function Button({ children, ...props }: ButtonProps) {
return <button className="btn" {...props}>{children}</button>;
}
function IconButton({ icon, ...props }: ButtonProps & { icon: string }) {
return (
<Button {...props}>
<Icon name={icon} />
{props.children}
</Button>
);
}State Management Patterns
Unidirectional Data Flow
// Actions
const addItem = (item: CartItem) => ({ type: 'ADD_ITEM', payload: item });
// Reducer
function cartReducer(state: CartState, action: CartAction) {
switch (action.type) {
case 'ADD_ITEM':
return { ...state, items: [...state.items, action.payload] };
default:
return state;
}
}
// Component
function Cart() {
const [state, dispatch] = useReducer(cartReducer, initialState);
return <CartView items={state.items} onAdd={(item) => dispatch(addItem(item))} />;
}Local vs Global State
// Local state - component-specific
function SearchBar() {
const [query, setQuery] = useState('');
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}
// Global state - application-wide
const UserContext = createContext<User | null>(null);
function App() {
const [user, setUser] = useState<User | null>(null);
return (
<UserContext.Provider value={user}>
<Router />
</UserContext.Provider>
);
}Design Patterns
MVC (Model-View-Controller)
// Model - data and business logic
class TodoModel {
private todos: Todo[] = [];
addTodo(text: string) {
this.todos.push({ id: Date.now(), text, completed: false });
this.notify();
}
}
// Controller - handles user input
class TodoController {
constructor(private model: TodoModel) {}
handleAddTodo(text: string) {
if (text.trim()) this.model.addTodo(text);
}
}
// View - React component
function TodoView({ controller, todos }: TodoViewProps) {
return (
<div>
<input onSubmit={e => controller.handleAddTodo(e.target.value)} />
<ul>{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}</ul>
</div>
);
}Observer Pattern
class Subject<T> {
private observers: Set<(data: T) => void> = new Set();
subscribe(observer: (data: T) => void) {
this.observers.add(observer);
return () => this.observers.delete(observer);
}
notify(data: T) {
this.observers.forEach(observer => observer(data));
}
}
// Usage
const userPresence = new Subject<UserPresenceData>();
function UserStatus({ userId }: { userId: string }) {
const [isOnline, setIsOnline] = useState(false);
useEffect(() => {
return userPresence.subscribe(data => {
if (data.userId === userId) setIsOnline(data.isOnline);
});
}, [userId]);
return <span>{isOnline ? 'Online' : 'Offline'}</span>;
}Factory Pattern
interface FormField {
render(): JSX.Element;
validate(): boolean;
getValue(): any;
}
class FormFieldFactory {
create(type: string, config: any): FormField {
switch (type) {
case 'text': return new TextField(config);
case 'select': return new SelectField(config);
case 'date': return new DateField(config);
default: throw new Error(`Unknown field type: ${type}`);
}
}
}
// Usage
function DynamicForm({ schema }: { schema: FormSchema }) {
const factory = new FormFieldFactory();
const fields = schema.fields.map(f => factory.create(f.type, f.config));
return (
<form>
{fields.map(field => field.render())}
</form>
);
}Module Systems and Code Splitting
ES Modules
// Exporting
export interface Logger { /* ... */ }
export class ConsoleLogger implements Logger { /* ... */ }
export default new ConsoleLogger();
// Importing
import logger, { Logger, ConsoleLogger } from './logger';
import type { Logger } from './logger'; // Type-only importCode Splitting Strategies
Route-Based Splitting
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}Component-Based Splitting
const HeavyComponent = lazy(() => import('./HeavyComponent'));
function FeaturePage() {
const [show, setShow] = useState(false);
return (
<div>
<button onClick={() => setShow(true)}>Load Feature</button>
{show && (
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
)}
</div>
);
}Dynamic Imports
async function loadExporter() {
const module = await import('./excel-exporter');
return new module.ExcelExporter();
}
function DataTable() {
const handleExport = async () => {
const exporter = await loadExporter();
exporter.export(data);
};
return <button onClick={handleExport}>Export</button>;
}Build Tools
Webpack Configuration
module.exports = {
entry: './src/index.tsx',
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
},
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors'
}
}
}
},
module: {
rules: [
{
test: /\.(ts|tsx)$/,
use: 'ts-loader'
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'postcss-loader']
}
]
}
};Vite Configuration
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
'@components': path.resolve(__dirname, './src/components')
}
},
build: {
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'ui-vendor': ['@radix-ui/react-dialog']
}
}
}
}
});Testing Architecture
Testing Pyramid
/\
/ \
/ E2E \ Few, slow, expensive
/______\
/ \
/ INT \ Some, medium speed
/__________\
/ \
/ UNIT \ Many, fast, cheap
/______________\Unit Tests
describe('calculateTotal', () => {
it('should sum item prices', () => {
expect(calculateTotal([{ price: 10, qty: 2 }])).toBe(20);
});
});Integration Tests
describe('LoginFlow', () => {
it('should authenticate user', async () => {
render(<LoginPage />);
await userEvent.type(screen.getByLabelText('Email'), 'user@test.com');
await userEvent.click(screen.getByText('Login'));
expect(await screen.findByText('Dashboard')).toBeInTheDocument();
});
});E2E Tests
test('complete checkout', async ({ page }) => {
await page.goto('/products');
await page.click('[data-testid="add-to-cart"]');
await page.click('[data-testid="checkout"]');
await page.fill('[name="card"]', '4242424242424242');
await page.click('[data-testid="place-order"]');
await expect(page.locator('[data-testid="success"]')).toBeVisible();
});Test Utilities
// Custom render with providers
export function renderWithProviders(
ui: React.ReactElement,
options?: RenderOptions
) {
function Wrapper({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<BrowserRouter>{children}</BrowserRouter>
</QueryClientProvider>
);
}
return render(ui, { wrapper: Wrapper, ...options });
}
// Mock factories
export function createMockUser(overrides?: Partial<User>): User {
return {
id: '1',
email: 'test@example.com',
name: 'Test User',
...overrides
};
}Performance Optimization
Tree Shaking
// Good - enables tree shaking
import { debounce } from 'lodash-es';
// Bad - imports entire library
import _ from 'lodash';Caching Strategies
Service Worker
const CACHE_NAME = 'app-v1';
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request)
.then(response => response || fetch(event.request))
);
});React Query
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 min
cacheTime: 10 * 60 * 1000 // 10 min
}
}
});Bundle Optimization
// Manual chunks in Vite
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'react-vendor': ['react', 'react-dom'],
'utils': ['date-fns', 'lodash-es']
}
}
}
}
});Scalable Folder Structure
Feature-Based Organization
src/
├── features/
│ ├── auth/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── services/
│ │ ├── types/
│ │ └── index.ts
│ ├── products/
│ └── cart/
├── shared/
│ ├── components/
│ ├── hooks/
│ └── utils/
├── core/
│ ├── api/
│ ├── router/
│ └── store/
└── assets/Naming Conventions
// Components
UserProfile.tsx
ProductCard.tsx
// Hooks
useAuth.ts
useLocalStorage.ts
// Utilities
formatDate.ts
debounce.ts
// Constants
API_BASE_URL
MAX_FILE_SIZE
// Types
User.types.ts
Product.types.tsBest Practices
1. Component Design
- Single Responsibility Principle
- Composition over inheritance
- Proper TypeScript typing
- Clear prop interfaces
2. State Management
- Choose appropriate state level
- Immutable updates
- Separate server/client state
- Avoid prop drilling
3. Performance
- Code splitting
- Lazy loading
- Tree shaking
- Proper caching
4. Testing
- Follow testing pyramid
- Test behavior, not implementation
- Use proper test utilities
- Mock external dependencies
5. Scalability
- Consistent structure
- Clear naming conventions
- Module boundaries
- Documentation
Common Pitfalls
- Over-engineering - Start simple, add complexity as needed
- Premature optimization - Measure before optimizing
- Tight coupling - Use dependency injection and clear interfaces
- Monolithic components - Break down into smaller pieces
- Global state overuse - Prefer local state when possible
- Ignoring types - Use TypeScript for better DX and fewer bugs
When to Use This Skill
Use this skill when you need to:
- Design a new application architecture
- Refactor existing code for better scalability
- Choose state management solutions
- Implement design patterns
- Optimize build and bundle configuration
- Structure testing strategies
- Improve application performance
- Scale codebases for growing teams
Related Skills
- react-patterns - React-specific patterns
- typescript-architecture - TypeScript design patterns
- performance-optimization - Advanced performance techniques
- testing-strategies - Testing best practices
- webpack-configuration - Deep dive into Webpack
- state-management - Redux, Zustand, Jotai patterns
Resources
- Architecture Patterns - Learn MVC, MVVM, Flux
- Component Design - Atomic design, composition patterns
- State Management - Redux, MobX, Zustand, Jotai
- Build Tools - Webpack, Vite, Rollup documentation
- Testing - Jest, React Testing Library, Playwright
- Performance - Web Vitals, Lighthouse, Bundle analyzers
Examples
See EXAMPLES.md for 15+ detailed architectural examples including:
- Full-featured application architectures
- State management implementations
- Build configurations
- Testing strategies
- Performance optimizations
- Real-world patterns and solutions
Related skills
How it compares
Use frontend-architecture for system-level SPA structure and module boundaries; use a framework-specific skill like react-development when implementing components inside an chosen architecture.
FAQ
What patterns does frontend-architecture cover?
frontend-architecture covers MVC, MVVM, and Flux pattern selection, component single-responsibility design, composition-over-inheritance, state management strategies, module federation, code splitting, lazy loading, routing, and performance budgets for TypeScript SPAs.
When should developers invoke frontend-architecture?
frontend-architecture applies when designing scalable application structure, choosing architectural patterns, implementing state management for complex apps, structuring component hierarchies, optimizing bundler performance, or refactoring legacy frontend code to modern layered p