The Challenge: Demonstrating Cross-Framework Fluency

As a Senior Full Stack & Mobile Developer, I often face the interview question: 'You list React, Vue, Angular, and Kotlin Multiplatform. Which are you most comfortable with?' Listing technologies on a CV is easy; proving mastery is another matter. Code speaks louder than words.

To address this, I embarked on an engineering challenge: building a single, identical application across four leading ecosystems. The goal was to allow users to switch between frameworks on the fly, without losing application state or their reading position. This wasn't about choosing a favorite; it was about demonstrating deep understanding and practical application of diverse frontend architectures.

The chosen frameworks were:

  • ⚛️ React 19 (Vite + Custom Hooks)
  • 💚 Vue 3.5 (Composition API + Composables)
  • 🅰️ Angular 19 (Standalone Components + RxJS)
  • 💧 Compose HTML (Jetpack Compose for Web)

The core requirement was seamless state management and user experience, regardless of the framework in use. This meant abstracting business logic and state away from the UI layer, a principle central to Clean Architecture.

Implementing Clean Architecture Across Frameworks

Clean Architecture, as popularized by Robert C. Martin, provides a set of principles for organizing code into layers. The central idea is dependency rule: dependencies can only point inwards. Outer layers know nothing about inner layers. This promotes testability, maintainability, and framework independence.

For this project, the layers were structured as follows:

  1. Domain Layer: Contains the core business logic, entities, and use cases. This layer is entirely framework-agnostic. It defines the rules of the application without any knowledge of UI or data sources.
  2. Application Layer: Orchestrates the domain layer. It coordinates the use cases and interacts with the infrastructure layer (repositories, services). It remains framework-agnostic but depends on the domain layer.
  3. Infrastructure Layer: Implements interfaces defined in the application layer. This includes data repositories, API clients, and any external service integrations. This is where framework-specific concerns start to appear, such as data fetching libraries or local storage implementations.
  4. Presentation Layer (UI): Responsible for displaying data to the user and capturing user input. This is the most framework-specific layer. It consumes data from the application layer (often via ViewModels or similar patterns) and renders it.

The key to making this work across React, Vue, Angular, and Compose HTML was to ensure that the Domain and Application layers were written in a way that could be consumed by any of these UI frameworks. This often involved using plain JavaScript/TypeScript for the core logic and then creating specific adapters or hooks for each framework to bridge the gap.

Diagram illustrating Clean Architecture layers and dependency flow

Framework-Specific Implementations and Challenges

Each framework presented unique opportunities and challenges:

React 19 (Vite + Custom Hooks)

Leveraging React 19's latest features, I focused on custom hooks to encapsulate reusable logic and state management. Vite provided a fast development experience. The challenge here was managing complex state across components, which custom hooks and a context API helped to address. The custom hooks acted as the bridge between the framework-agnostic domain logic and React's component lifecycle.

Vue 3.5 (Composition API + Composables)

Vue's Composition API and composables were a natural fit for implementing Clean Architecture principles. Composables allowed for the extraction of reactive logic, mirroring the concept of use cases or domain services. This made it straightforward to integrate the business logic layer into Vue components. The state management was handled elegantly using Vue's reactivity system.

Angular 19 (Standalone Components + RxJS)

Angular's shift towards standalone components simplified the architecture, reducing boilerplate. RxJS was instrumental in managing asynchronous operations and state streams, aligning well with the event-driven nature of Clean Architecture. The main challenge was ensuring that the RxJS streams from the application layer were correctly subscribed to and managed within the Angular component lifecycle, preventing memory leaks.

The use of services in Angular naturally mapped to the infrastructure or application layers, depending on their responsibility. For instance, a data service could abstract API calls (infrastructure), while a business logic service could orchestrate domain use cases (application).

Example of Angular service interacting with domain logic

Compose HTML (Jetpack Compose for Web)

Jetpack Compose for Web (Compose HTML) offered a declarative UI paradigm similar to React or SwiftUI. The challenge here was the relative novelty of Compose for web compared to the others. Implementing Clean Architecture meant defining state holders and observing them within composable functions. The framework-agnostic domain logic was called from view models or directly from composables, with state updates triggering recomposition. This provided a consistent declarative experience while adhering to the architectural separation.

The