Towards a web architecture based on intent and communication
Frontend JavaScript applications built on virtual DOM libraries such as React (Facebook Inc., 2021) have grown considerably in complexity over the past decade. A recurring challenge is that developers reach for server-side architectural styles, most notably MVC and the Clean Architecture (Martin, 2018), and apply them to a problem domain those styles were never designed to address. The result is a mismatch: folder structures that mirror backend conventions but obscure the specific concerns of a user-facing, stateful, browser-executed application.
This post revisits and expands a research paper submitted in 2021 to PROWEB that proposed an architectural style for web applications based on intent and communication (Marabesi, 2021). The paper was rejected, yet the core observations remain accurate and have since been corroborated by the broader industry movement toward feature-based organization, microfrontends, and explicit data-flow boundaries.
Why server-side architectures fall short on the frontend
The Clean Architecture (Martin, 2018) divides a system into four concentric layers: entities, use cases, interface adapters, and frameworks and drivers. Its explicit premise is that the UI is an implementation detail, a detail to be kept in the outermost layer. That premise works well for backend services whose primary obligation is to execute business rules, persist data, and expose APIs.
A browser application has different primary obligations. It must render a consistent user interface across browser vendors, manage local component state and global application state, integrate with third-party SDKs (analytics, payment providers, design systems), respond to user events in sub-100 ms latency, and orchestrate asynchronous data fetching. None of these concerns map cleanly onto “entities” or “use cases”.
(Martin, 2012) acknowledges this gap by deferring the definition of the presentation layer to MVC. However, MVC as described for Smalltalk by (Grov, 2015) does not address global state management, third-party library coupling, or the bidirectional data flows that emerge in real-time features such as chat or live dashboards.
The Hexagonal Architecture (Cockburn, 2005) and the Onion Architecture (Palermo, 2008) face the same limitation. They are port-and-adapter models concerned with isolating the application core from infrastructure, not with structuring the presentation tier itself.
The clean architecture applied to React: a common experiment
Several open-source projects have attempted a direct translation of the Clean Architecture into React folder structures. The typical outcome is four top-level directories (entities, use-cases, adapters, infrastructure) that mirror the concentric diagram. While this forces separation of persistence from business rules, it conflates the UI component tree with “adapters” and provides no guidance for where Redux reducers, React Query caches, or Zustand stores belong. Developers end up scattering state management code across the use-case and adapter layers depending on personal preference, which defeats the intent of explicit architectural boundaries. (Hombergs, 2019) acknowledges this explicitly, noting that the clean architecture book does not describe how to structure a user-facing application’s presentation layer.
The four pillars of the intent-based architecture
The 2021 paper (Marabesi, 2021) proposes structuring a frontend codebase around four explicit pillars, each of which encapsulates a distinct category of concern.
UI (user interface)
The UI layer contains pure presentational components: components that receive data through props (or slots/inputs, depending on the framework) and render visual output. They carry no data-fetching logic, no direct calls to state management libraries, and no imports of third-party SDKs. Their single responsibility is to transform a data shape into a rendered subtree.
This discipline is directly consistent with the “presentational and container components” pattern popularised in the React community, where presentational components are described as concerned with “how things look” and are free of application state (Facebook Inc., 2021).
Third party
The third-party layer acts as an anti-corruption boundary between the application and external dependencies. Every external library that is consumed in more than one place in the codebase is wrapped here. If the dependency is replaced or upgraded with a breaking API change, only the wrapper needs to change.
This mirrors the Strangler Fig pattern (Fowler, 2004) applied at the dependency level: the wrapper absorbs the surface area of external churn and protects the application internals. Practical examples include wrapping a date library (e.g., Day.js replacing Moment.js without touching 200 call sites), wrapping an analytics SDK so the application code emits named domain events rather than vendor-specific method calls, and wrapping a design-system library’s button component with the application’s own prop conventions.
Data flow
The data-flow layer contains everything that governs the movement and transformation of data in the application: Redux reducers and actions, MobX observables, React Query query definitions, Zustand stores, or equivalent constructs in other frameworks. These artifacts are not part of the UI subtree and are not part of pure business logic; they form the connective tissue between user interactions, remote APIs, and the component tree.
Isolating data-flow concerns in a dedicated layer makes state topology visible. Developers can read the data-flow layer and understand the full set of application states without tracing through component hierarchies. It also enables independent testing: state machines and query definitions can be exercised in unit tests without mounting any component.
Packages
The packages layer holds pure business logic that is free from external-library dependencies and state-management concerns. This is the closest analogue to the “use cases” or “entities” layer in the Clean Architecture, transposed to the frontend context. Examples include: a utility that calculates a shipping cost from a set of rules, a function that validates a form against business constraints, or a module that transforms a raw API response into a domain object.
The separation between packages and the data-flow layer is deliberate. A shipping-cost calculator should not import Redux. A validation function should not call React Query. Keeping them separate allows these units to be reused across different data-flow strategies and makes them trivial to test with plain function calls.
The gray area between UI and data flow
Real-time features expose the most difficult boundary in this architecture. A WebSocket-driven chat feature, for example, receives incoming messages passively (server pushes to client) and also sends messages on user interaction (client pushes to server). The incoming messages update the UI automatically through the data-flow layer, while outgoing messages originate in the UI layer and travel through the data-flow layer to the network. This bidirectional coupling is acknowledged in the 2021 paper as a “gray area” that requires explicit team conventions. One practical resolution is to treat the WebSocket connection itself as a third-party concern (wrapping it in the third-party layer), surface its events through the data-flow layer, and have the UI layer dispatch actions rather than calling the socket directly.
How the architecture maps to a folder structure
A concrete folder layout for a React application following these four pillars might look like the following.
src/
ui/
Button/
Header/
ProductCard/
third-party/
analytics/
date/
design-system/
data-flow/
cart/
products/
user/
packages/
pricing/
validation/
formatting/
The key constraint is that dependencies only flow inward: ui may import from data-flow, third-party, and packages; data-flow may import from packages; packages imports nothing from the other three layers; third-party imports from external dependencies only. This rule can be enforced with static analysis tools such as ESLint’s import/no-restricted-paths rule or dedicated architectural linting tools.
Relationship to modern architectural approaches
Feature-sliced design
Feature-Sliced Design (FSD) is a methodology that has gained traction in the React community since 2021. It organises source code into layers (app, pages, widgets, features, entities, shared) and slices (business domains). The parallels to the intent-based architecture are clear: FSD’s “shared” layer maps to a combination of the third-party and packages pillars; its “entities” and “features” layers map to the data-flow and packages pillars; its “widgets” and “pages” layers map to the UI pillar.
The intent-based architecture predates FSD’s formal documentation and arrives at similar conclusions from a different starting point (the Clean Architecture critique rather than a bottom-up community process). Together, they reinforce that explicit, named boundaries between UI rendering, state management, external dependencies, and business logic are a stable engineering practice regardless of framework.
Microfrontends
Microfrontends (Peltonen et al., 2021) take the decomposition one level higher, splitting the application across independently deployable units owned by separate teams. The intent-based architecture is complementary: within each microfrontend, the four-pillar structure provides intra-module organisation, while the microfrontend boundary provides inter-module isolation (Pavlenko et al., 2020).
Webpack Module Federation (Jackson, 2020) makes the boundary between microfrontends explicit at the runtime module level. When each microfrontend applies the intent-based structure internally, the shared contracts it exposes across the federation boundary (components, state hooks, utility functions) are easier to categorise: the host application knows whether it is receiving a UI component, a data-flow subscription, or a pure business-logic function.
React Server Components
React Server Components, introduced experimentally in 2020 and stabilised in the React 18 and Next.js 13 cycles, add a new axis to frontend architecture: the server/client execution boundary. A Server Component fetches data on the server and renders static HTML; a Client Component manages interactivity in the browser.
This distinction maps naturally onto the intent-based pillars. Server Components are a UI concern (the server renders them), but their data-fetching logic belongs in the data-flow layer even when executed on the server. Pure business logic in the packages layer can be called from both server and client components without modification. The third-party layer may need to be split into server-safe and client-only wrappers, since some external SDKs (e.g., browser analytics) cannot run on the server.
Testing benefits of the layered structure
A secondary finding of the 2021 paper, supported by the separation of concerns principle in (Richards, 2015), is that explicit layers lower the cost of automated testing.
- Packages are tested with pure unit tests. No DOM, no network, no state setup.
- Data-flow units (reducers, selectors, query configurations) are tested with unit tests that provide initial state and assert on output state, without mounting any component.
- Third-party wrappers are tested with integration tests against the real external library or a controlled stub, limiting the blast radius of a library upgrade.
- UI components are tested with component-level rendering tests (React Testing Library, Storybook interaction tests) that pass props and assert on rendered output, without involving the data-flow layer.
Each layer’s tests are fast, focused, and independent. A failing test in the packages layer does not require debugging a component tree. A failing component test does not require tracing through a Redux action chain.
Applying the architecture incrementally
A full upfront reorganisation of an existing codebase is rarely practical. The Strangler Fig pattern (Fowler, 2004) applies here as well: identify the next new feature and build it following the four-pillar structure; over time, migrate existing code feature by feature.
A pragmatic sequence for an existing React application:
- Extract all third-party imports that appear in more than two components into a
third-partydirectory and replace the direct imports with calls to the wrapper. - Move all Redux or Zustand store definitions (including selectors and actions) into a
data-flowdirectory, one subdirectory per domain slice. - Move validation, calculation, and transformation functions that have no external dependencies into a
packagesdirectory. - The remaining components, now stripped of state-management and third-party imports, form the
uilayer.
Each step is independently verifiable by running the existing test suite, ensuring that the refactoring does not alter behaviour.
Limitations and open questions
The architecture described in the original paper (Marabesi, 2021) and expanded here is not formally evaluated against controlled empirical studies. The paper itself acknowledges this gap in its future-work section, calling for research that measures whether the architecture provides benefits in real projects regardless of the framework used.
Specific open questions include:
- How does the architecture interact with co-location conventions, where components, their tests, and their styles live in the same directory? The four-pillar layout places these artefacts in different directories by type, which conflicts with feature-based co-location.
- Does the packages layer duplicate the “entities” and “use cases” concept of the Clean Architecture sufficiently, or does it conflate concerns that should be separated further in large applications?
- What automated tooling can enforce the dependency rules (packages imports nothing from data-flow, data-flow does not import from ui) at commit time without significant developer overhead?
These are tractable research questions that can be addressed through case studies on open-source projects that adopt the structure.
References
- Marabesi, M. (2021). Towards a web architecture structure based on intent.
- Martin, R. C. (2018). Clean Architecture: A Craftsman’s Guide to Software Structure and Design. Prentice Hall. https://cds.cern.ch/record/2288410
- Martin, R. C. (2012). The Clean Architecture. https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
- Cockburn, A. (2005). Hexagonal Architecture. https://alistair.cockburn.us/hexagonal-architecture
- Richards, M. (2015). Software Architecture Patterns. O’Reilly Media. https://www.oreilly.com/library/view/software-architecture-patterns/9781491971437
- Hombergs, T. (2019). Get Your Hands Dirty on Clean Architecture. Packt Publishing.
- Grov, M. (2015). Building User Interfaces Using Virtual DOM [Master's thesis]. Norwegian University of Science and Technology.
- Peltonen, S., Mezzalira, L., & Taibi, D. (2021). Motivations, benefits, and issues for adopting Micro-Frontends: A Multivocal Literature Review. Information and Software Technology, 136, 106571. https://doi.org/10.1016/j.infsof.2021.106571
- Facebook Inc. (2021). React: A JavaScript Library for Building User Interfaces. https://reactjs.org
- Pavlenko, A., Askarbekuly, N., Megha, S., & Mazzara, M. (2020). Micro-frontends: application of microservices to web front-ends. Journal of Internet Services and Applications, 11(1), 1–12. https://doi.org/10.1186/s13174-020-00128-5
- Jackson, Z. (2020). Webpack 5 Module Federation: A game-changer in JavaScript architecture. https://webpack.js.org/concepts/module-federation
- Steyer, M. (2023). Sheriff: Lightweight Architectural Linting for Feature-Sliced Design in Angular. https://www.angulararchitects.io/blog/sustainable-angular-architectures-1
- Palermo, J. (2008). The Onion Architecture: Part 1. https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1
- Fowler, M. (2004). StranglerFigApplication. https://martinfowler.com/bliki/StranglerFigApplication.html
You also might like
About this post
This post was written by a human with AI assistance, which was used for research, outlining, editing and code suggestions. Claims, code samples and references were reviewed before publication, and the sources are cited so you can judge them yourself.