System Live
BUILD IN PUBLIC: Updating Neural Engine v2.1.0... | New blog post: Rust for AI optimization... | System Uptime: 99.98% Across All Sectors... | Compiling Archive 404: Logic Integrity 100%... | Deploying Lab Sector Delta: Connectivity Established... | Neural Link: Active... Memory Buffer: Clear... | AI Solver: Processing Complex Engineering Query... | BUILD IN PUBLIC: Updating Neural Engine v2.1.0... | New blog post: Rust for AI optimization... | System Uptime: 99.98% Across All Sectors... | Compiling Archive 404: Logic Integrity 100%... | Deploying Lab Sector Delta: Connectivity Established... | Neural Link: Active... Memory Buffer: Clear... | AI Solver: Processing Complex Engineering Query... |
Home/Blogs/Building Modern Web Apps: Architecture Best Practices

Deep Dive

Building Modern Web Apps: Architecture Best Practices

Explore architectural patterns for scalable web applications. Learn about separation of concerns, state management, and building for production.

Lead Level
schedule4 min read
calendar_todayJan 25, 2026

Building Modern Web Apps

Creating production-ready web applications requires more than just knowing a framework. You need solid architecture, clear separation of concerns, and scalable patterns.

Application Architecture Layers

A well-structured application separates concerns into distinct layers:

text
1
2
3
4
5
6
7
8
9
10
┌─────────────────────────────────┐
│   Presentation Layer (UI)       │ ← React Components
├─────────────────────────────────┤
│   Business Logic Layer          │ ← Services, Utilities
├─────────────────────────────────┤
│   Data Access Layer             │ ← API clients, DB queries
├─────────────────────────────────┤
│   External Services             │ ← APIs, Databases, CDNs
└─────────────────────────────────┘

Folder Structure

Here's a battle-tested folder structure:

text
1
2
3
4
5
6
7
8
9
10
11
12
13
src/
├── app/              # Next.js App Router pages
├── components/       # Reusable UI components
│   ├── ui/          # Base components (Button, Input)
│   ├── features/    # Feature-specific components
│   └── layouts/     # Layout components
├── lib/             # Business logic
│   ├── services/    # External API clients
│   ├── utils/       # Helper functions
│   └── hooks/       # Custom React hooks
├── types/           # TypeScript type definitions
└── config/          # Configuration files

Component Design Principles

1. Single Responsibility

Each component should do one thing well:

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ❌ Bad: Component does too much
function UserDashboard() {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);
  const [analytics, setAnalytics] = useState(null);
  
  // Fetching logic, rendering logic, all mixed together
  // ...
}

// ✅ Good: Separated concerns
function UserDashboard() {
  return (
    <>
      <UserProfile />
      <UserPosts />
      <UserAnalytics />
    </>
  );
}

2. Composition Over Inheritance

Build complex UIs from simple, composable pieces:

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Composable Card component
interface CardProps {
  children: React.ReactNode;
  variant?: 'default' | 'outlined' | 'elevated';
}

function Card({ children, variant = 'default' }: CardProps) {
  return <div className={`card card-${variant}`}>{children}</div>;
}

// Usage - compose cards with other components
<Card variant="elevated">
  <CardHeader title="User Stats" />
  <CardContent>
    <StatsChart data={stats} />
  </CardContent>
</Card>

State Management Strategy

Local State (useState)

For component-specific state:

typescript
1
2
3
4
5
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

Server State (React Query / SWR)

For data from external sources:

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
import useSWR from 'swr';

function UserProfile({ userId }: { userId: string }) {
  const { data, error, isLoading } = useSWR(
    `/api/users/${userId}`,
    fetcher
  );
  
  if (isLoading) return <Skeleton />;
  if (error) return <ErrorState />;
  return <ProfileCard user={data} />;
}

Global State (Context / Zustand)

For app-wide state:

typescript
1
2
3
4
5
6
7
8
9
10
// Zustand store
import create from 'zustand';

const useThemeStore = create((set) => ({
  theme: 'light',
  toggleTheme: () => set((state) => ({ 
    theme: state.theme === 'light' ? 'dark' : 'light' 
  })),
}));

Error Handling

Robust applications handle errors gracefully:

typescript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Error Boundary for React errors
function ErrorBoundary({ children }: { children: React.ReactNode }) {
  return (
    <Suspense fallback={<Loading />}>
      {children}
    </Suspense>
  );
}

// Async error handling
async function fetchWithRetry(url: string, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(url);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return await response.json();
    } catch (error) {
      if (i === retries - 1) throw error;
      await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
    }
  }
}

Performance Optimization

Code Splitting

Load code only when needed:

typescript
1
2
3
4
5
6
// Dynamic imports
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
  loading: () => <Spinner />,
  ssr: false, // Skip server-side rendering if not needed
});

Memoization

Prevent unnecessary re-renders:

typescript
1
2
3
4
5
6
7
8
9
10
const MemoizedComponent = memo(function ExpensiveComponent({ data }) {
  // Complex rendering logic
  return <div>{/* ... */}</div>;
});

// Memoize expensive calculations
const processedData = useMemo(() => {
  return expensiveOperation(rawData);
}, [rawData]);

Testing Strategy

Test Pyramid

text
1
2
3
4
5
6
7
8
        ┌──────────┐
        │   E2E    │  ← Few, high-value tests
        ├──────────┤
        │Integration│  ← Moderate coverage
        ├──────────┤
        │   Unit   │  ← Many, fast tests
        └──────────┘

Deployment Checklist

Before deploying to production:

  • [ ] Environment variables configured
  • [ ] Error tracking setup (Sentry, etc.)
  • [ ] Analytics integrated
  • [ ] SEO metadata complete
  • [ ] Performance audited (Lighthouse)
  • [ ] Security headers configured
  • [ ] Database migrations tested
  • [ ] Rollback plan documented

Conclusion

Building modern web applications is about making intentional architectural decisions:

  1. Separate concerns into clear layers
  2. Compose components from small, focused pieces
  3. Manage state appropriately for each use case
  4. Handle errors gracefully
  5. Test strategically
  6. Optimize based on real metrics

Start with solid foundations, and scale as your application grows!