HomeBytesheetsRoadmapsUI TemplatesUI ToolsStack Discussion
6 Topics
Chapter•105 Minutes

Production React Architecture & Design Patterns

Design scalable feature boundaries, reusable UI primitives, data layers, error handling, dependency direction, and maintainable application architecture.

Feature-Based Architecture

A scalable React codebase usually benefits from organizing code around features or domains rather than technical categories alone. A feature can own components, hooks, API clients, types, validation, and tests.

Shared code should be genuinely shared. Do not move something into shared/ simply because a second file imports it. Premature sharing can create generic abstractions before the domain is understood.

A good architecture makes it easy to answer: where does this behavior live, who owns it, and what is allowed to depend on it?

Feature boundary
src/features/billing/
  components/
  hooks/
  api/
  model/
  validation/
  billing.routes.tsx
  index.ts
💡 The feature owns its internal implementation while an index can expose a small public API.
Key Takeaways
  • Organize around domain ownership when scale demands it.
  • Keep shared code truly generic.
  • Make dependency direction explicit.

Reusable UI Primitives & Design Systems

A design system provides stable primitives such as Button, Input, Dialog, Stack, Text, and FormField. These primitives should encode accessibility and consistent behavior so product features do not reinvent them.

Avoid a giant component with dozens of boolean props. Prefer composition and variants with clear semantics.

A design system is a product and engineering contract: changes can affect many consumers, so versioning, documentation, visual testing, and migration strategies matter.

Variant-oriented button
type ButtonProps = {
  variant?: "primary" | "secondary" | "danger";
  children: React.ReactNode;
};
💡 Explicit variants communicate supported design decisions better than arbitrary style flags.
Key Takeaways
  • Centralize accessibility-critical primitives.
  • Prefer explicit variants over boolean-prop explosions.
  • Treat design-system APIs as stable contracts.

API Clients & Data Access Boundaries

Keep HTTP concerns out of every UI component. A dedicated data-access layer can handle URL construction, authentication headers, parsing, retries, and typed responses.

Components should ideally ask for domain operations rather than know every transport detail. This improves testability and makes migrations easier.

Still, do not over-engineer a thin wrapper around fetch if the application has only a few endpoints. Introduce an abstraction when it provides a meaningful boundary.

Domain API function
export async function getUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  if (!response.ok) throw new Error("Failed to load user");
  return response.json();
}
💡 The component can consume a domain operation without knowing how the URL or response parsing works.
Key Takeaways
  • Centralize repeated transport behavior.
  • Expose domain-oriented operations where useful.
  • Avoid abstractions that do not reduce complexity.

Dependency Direction & Coupling

A healthy dependency graph generally points from high-level features toward lower-level reusable infrastructure. Circular dependencies and feature-to-feature imports should be deliberate and rare.

Use public module boundaries to prevent consumers from reaching deep into another feature's internals. This allows refactoring without breaking unrelated code.

Architecture is not only folder structure. It is the set of dependencies the codebase permits.

Key Takeaways
  • Control what modules are allowed to depend on.
  • Expose stable public APIs for features.
  • Architecture should constrain coupling, not merely document it.

Logging, Errors & Observability

Production React applications need visibility into failures and performance. Capture route, feature, operation, and correlation context where appropriate, while avoiding secrets and unnecessary personal data.

User-facing errors should be actionable and understandable. Developer logs should contain enough technical context to diagnose the problem.

Observability should distinguish expected user errors from infrastructure failures so dashboards do not become noise.

Key Takeaways
  • Separate user-facing messages from diagnostic details.
  • Include useful context without leaking secrets.
  • Track error rates and performance trends, not only console logs.

Frontend Security Boundaries

A React frontend runs in an adversarial environment. Users can inspect and modify client-side JavaScript, requests, and local storage. Therefore, authorization must be enforced on the server.

Avoid injecting untrusted HTML. Prefer React's normal text rendering and sanitize content when HTML must be rendered from untrusted sources. Protect authentication tokens according to the application's threat model and understand the tradeoffs between cookies, memory, and browser storage.

Frontend validation improves UX; server authorization and validation provide security.

Never trust the client

A hidden button, disabled input, or client-side role check is not authorization. The server must enforce permissions.

Key Takeaways
  • The browser is not a trusted security boundary.
  • Avoid unsafe HTML injection.
  • Authorization must be enforced server-side.