[ SYSTEM ] / LOGBOOK / arquitectura-hexagonal-frontend

Hexagonal Architecture in Frontend: Scalability & Decoupling

Learn how to implement Hexagonal Architecture (Ports & Adapters) in React and Astro. Decouple business logic from the UI to build scalable web applications.

DATE: 11/20/2025
Hexagonal Architecture in Frontend: Scalability & Decoupling

The Frontend ecosystem is inherently chaotic. Frameworks change, state management libraries become obsolete, and backend APIs evolve rapidly. If your business logic is tightly coupled to your React or Astro components, your technical debt will grow exponentially.

Hexagonal Architecture (or Ports and Adapters), conceptualized by Alistair Cockburn, has been a backend staple for decades. Today, applying it in the client (Frontend) is no longer an extravagance; it is a strict requirement for enterprise-grade applications.

Why Does the Frontend Need Decoupling?

Imagine you are building a complex e-commerce platform. The typical developer flow involves writing a useEffect that calls fetch and updates the local state using useState.

// ❌ Anti-pattern: Infrastructure logic coupled to the UI
function ProductList() {
  const [products, setProducts] = useState([]);
  
  useEffect(() => {
    fetch('/api/v1/products')
      .then(res => res.json())
      .then(data => setProducts(data));
  }, []);

  return <div>{/* Render logic */}</div>;
}

What happens if you migrate your API to GraphQL? What if you need to mock the data for unit tests? You are forced to rewrite the UI components.

Ports and Adapters on the Client

In a Hexagonal Architecture, the system is divided into three main layers:

  1. Domain: Entities and business rules. Pure TypeScript, zero dependencies on React or the browser.
  2. Application (Use Cases): Orchestrates data flow. Defines interfaces (Ports).
  3. Infrastructure: The outer layer. Contains the UI (React/Astro) and the Adapters (Axios, Fetch, LocalStorage).

The Dependency Inversion Flow

To achieve this decoupling, we utilize the Dependency Inversion Principle (the ‘D’ in SOLID).

// 1. Define the Port (Domain/Application)
export interface ProductRepository {
  getProducts(): Promise<Product[]>;
  getProductById(id: string): Promise<Product>;
}

// 2. Implement the Adapter (Infrastructure)
export class ApiProductRepository implements ProductRepository {
  async getProducts(): Promise<Product[]> {
    const res = await fetch('https://api.company.com/products');
    return res.json();
  }
}

Injecting Dependencies into React

Instead of the component knowing how to fetch the data, we simply inject the use case or the repository.

// ✅ Decoupled Component
interface Props {
  repository: ProductRepository;
}

export function ProductList({ repository }: Props) {
  const [products, setProducts] = useState<Product[]>([]);

  useEffect(() => {
    repository.getProducts().then(setProducts);
  }, [repository]);

  return (
    <ul>
      {products.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
  );
}

Benefits of this Approach

  1. Absolute Testability: You can create a MockProductRepository that returns static data. Your UI tests will run in milliseconds without network interceptions.
  2. Zero-Friction Migrations: Moving from React to Vue? The domain and network adapters remain intact.
  3. Parallel Development: The Frontend team can work with fake adapters (In-Memory) while the Backend team finishes the real API.

Conclusion

Hexagonal Architecture demands an initial investment of time and boilerplate code. Do not use it for landing pages or simple projects. However, if you are building a complex SaaS, a B2B dashboard, or a high-performance Fintech platform, this separation model will guarantee unprecedented technical scalability.