Select a theme mode
Navigation
Select a theme mode

Framework-Agnostic Frontend Architecture

10 min read
Draft

This document defines the architectural principles for building modern frontend applications. The architecture uses a modular, feature-driven approach to improve scalability and maintainability, regardless of the specific framework (for example, React, Vue, Solid, or Svelte).

File Naming Convention: Use kebab-case for all file and directory names (for example, my-component.js or user-profile/). Component names in code should follow the standard convention for the chosen framework or library (for example, PascalCase).

Root Directory: src/

The src/ directory contains all source code for the application.

1. domains/ - Feature Modules

The domains/ directory contains the application’s features. Each sub-directory in domains/ is a self-contained feature module that encapsulates all logic for a specific business domain (for example, user-profile/ or product-listing/). This structure enables better code separation and allows teams to work on features independently.

1.0. Domain Structure

Each feature folder encapsulates all code for that feature. Place test files in __tests__ sub-directories inside the directory of the code being tested. A typical domain can include some or all of the following sub-directories. Create these folders only when needed.

  • README.md: Explains the domain’s purpose, key responsibilities, business rules, and interactions.

  • assets/: Feature-specific static assets organized by type:

    • assets/images/ (optional): Domain-specific images, illustrations, and graphics.
    • assets/icons/ (optional): Feature-specific icons and SVG assets.
    • assets/fonts/ (optional): Domain-specific typography assets.
    • assets/videos/ (optional): Feature-related multimedia content.
    • assets/documents/ (optional): Domain-specific PDFs, guides, or documentation files.

    Note: Domain assets follow the same organizational pattern as global assets (see section 4), but contain content specific to that feature only. Create asset subfolders only when you have multiple assets of that type. Simple domains might place all assets directly in the assets/ folder.

  • components/: UI components used only within this feature.

  • logic/: Modules containing stateful or business logic. For complex domains, this can be split into application/ and domain/ as described below.

  • views/: Top-level presentational components. A route renders a view.

  • [feature-name]-routes.js: Route definitions for the feature (for configuration-based routing).

  • store/ (optional): Feature-specific state management modules.

  • types/ (optional): TypeScript type definitions specific to the domain.

  • translations/ (optional): Internationalization files for the feature.

  • utils/ (optional): Helper functions used only within the feature.

  • layouts/ (optional): Layout components used only by views within this feature.

1.1. Advanced: Separating Application and Domain Logic

For domains with significant business complexity, it is highly recommended to further separate logic into two distinct categories: Application Logic and Domain Logic (Models). This is a core principle of Domain-Driven Design (DDD) that greatly improves testability and clarity.

  • Domain Logic (logic/domain/ or logic/models/): This is the core of your feature. It contains pure, framework-agnostic business rules and behavior. A model is a software representation of a real-world concept (e.g., a ShoppingCart or a User). It knows nothing about the UI, APIs, or your chosen framework.
  • Application Logic (logic/application/ or logic/use-cases/): This layer orchestrates the domain logic. It acts as the bridge between the UI and the domain models. Hooks, controllers, or composables live here. They are responsible for handling user input, fetching data, and calling methods on the domain models.

Example: A Shopping Cart Domain

  1. The Domain Model (domains/shopping-cart/logic/domain/cart.js)

This pure class represents the idea of a cart and enforces its rules.

   // This is the "model". It's pure business logic.  
   export class Cart {  
      constructor(items = []) { this.items = items; }  
     addItem(newItem) {  
       if (newItem.stock < 1) { throw new Error("Cannot add out-of-stock items."); }  
       this.items.push(newItem);  
     }  
     calculateTotal() { return this.items.reduce((total, item) => total + item.price,
     0); }  
   }
  1. The Application Logic (domains/shopping-cart/logic/application/use-cart.js)

This React hook uses the model to connect it to the application.

2. The Application Logic (domains/shopping-cart/logic/application/use-cart.js)  
   
   This React hook uses the model to connect it to the application.  
   
   import { Cart } from `../domain/cart.js`;  
   import { api } from `../../../services/api.js`;  
   import { useState, useEffect } from 'react';  
   export function useCart() {  
     const [cartModel, setCartModel] = useState(new Cart());  
     const [isLoading, setIsLoading] = useState(false);  
     useEffect(() => {  
       setIsLoading(true);  
       api.get(`/cart`).then(data => setCartModel(new Cart(data.items))).finally(() => setIsLoading(false));  
     }, []);  
     function handleAddItemToCart(item) {  
       const newCart = new Cart([...cartModel.items]);  
       try {  
         newCart.addItem(item); // Use the model's business logic  
         setCartModel(newCart);  
       } catch (error) { alert(error.message); }  
     }  
     return { cart: cartModel, isLoading, handleAddItemToCart };  
   }

1.2. Inter-Domain Code Sharing: An Anti-Pattern

Directly importing code between domains is an anti-pattern. It creates tight coupling and undermines modularity.

The Solution: Promote Shared Code
If logic from one domain is needed in another, promote it to a global directory (src/utils/, src/logic/, etc.) and have both domains import it from there.

1.3. Inter-Domain Communication: An Anti-Pattern

Just as direct code imports are prohibited, direct runtime communication (e.g., one domain calling a function in another) is also an anti-pattern.

The Solution: Mediate with a Global Event Bus
Domains must communicate indirectly through a global event bus (a publish/subscribe system), likely defined in src/services/event-bus.js.

  • Publish: A domain sends an event to the bus, unaware of who is listening.
  • Subscribe: Other domains listen for events they care about.

This ensures domains remain completely decoupled.

2. ui/ - Global UI Components

The central library for globally reusable UI components. Do not use barrel files (index.js).

  • ui/core/: Atomic, abstract building blocks (e.g., button, input).
  • ui/patterns/ (Optional): Composed components for common, opinionated UI tasks (e.g., confirm-modal).
  • ui/lab/ (Optional): Experimental or highly specialized components.

3. Routing

  • Configuration-Based (React Router, Vue Router): A central router/ directory aggregates route configurations from each domain’s [feature-name]-routes.js file.
  • File-Based (Next.js, SvelteKit): A top-level src/pages/ or src/routes/ directory defines the URL structure. These files should be lean, fetching data and rendering a view from the relevant domains/ directory.

4. Other Global Directories

  • layouts/: Global page structure templates (e.g., app shell).
  • assets/: Global static resources organized by type:
    • assets/images/: Brand logos, hero backgrounds, global illustrations, and placeholder images.
    • assets/fonts/: Typography assets like web fonts (.woff2, .woff, .ttf files).
    • assets/icons/: Icon systems including SVG sprites, favicon files, and global icon assets.
    • assets/videos/: Brand videos, background videos, and other multimedia content.
    • assets/documents/: Global PDFs, downloadable content, and documentation files.
  • styles/: Global stylesheets, CSS resets, and design tokens (theme.css).
  • logic/: Global stateful or business logic.
  • utils/: Global, stateless utility functions.
  • services/: Global, shared services that handle cross-cutting concerns or infrastructure interactions. Examples include API clients, a global event bus, logging services, or browser API wrappers.
  • store/: Global state management.
  • types/: Globally shared TypeScript types.

5. Testing and Documentation

  • Co-location: Place unit tests in a __tests__/ subdirectory. Place README.md files inside any significant folder to explain its purpose.
  • Project Level: Use a root tests/ for E2E tests and a docs/ for high-level documentation. The docs/ directory should contain:
    • architecture.md: This document.
    • onboarding-guide.md: Instructions for new developers.
    • adr/: A directory for Architectural Decision Records (ADRs).

6. Relationship to Vertical Slice Architecture

This architecture is a direct application of the Vertical Slice Architecture (VSA) philosophy, adapted for modern frontend development. Instead of organizing code by technical layers (e.g., a global components/ folder, a global hooks/ folder), it organizes code by feature.

  • High Cohesion: All the code needed for a single feature (the UI, logic, assets) lives together in one place (domains/[feature-name]/), making it easy to reason about and modify.
  • Low Coupling: Strict rules against direct inter-domain communication ensure that features are independent and can be developed in parallel without interfering with one another.
  • Frontend Adaptation: In a backend context, a vertical slice runs from the API endpoint to the database. In this frontend architecture, a slice runs from the user interface (the URL and the view) down to the API client layer (services/api/).

7. Glossary

  • ADR (Architectural Decision Record): A document that captures an important architectural decision, its context, and the consequences. ADRs provide a historical record of why the system is built the way it is.
  • Anti-Pattern: A common response to a recurring problem that appears to be a good solution but ultimately creates more problems.
  • Application Logic: The layer that orchestrates domain models and connects them to the UI and infrastructure. Often implemented as hooks or controllers.
  • Barrel File: A single index.js file that exports all other modules in a directory. Discouraged in this architecture to improve tree-shaking.
  • Co-location: The practice of placing related files together in the same directory. In this architecture, tests (__tests__/) and documentation (README.md) are co-located with the code they describe.
  • Cohesion: The degree to which the elements inside a module belong together. This architecture aims for high cohesion by grouping all code related to a single feature within the same domain folder.
  • Core Component: A global UI component from ui/core/ that is atomic, abstract, and highly reusable (e.g., button, input).
  • Coupling: The degree to which one module depends on another. This architecture aims for low coupling by forbidding direct communication between domains.
  • Domain / Feature: A self-contained, vertical slice of the application corresponding to a specific business capability.
  • Event Bus: A global, publish-subscribe mechanism that allows different parts of an application (such as domains) to communicate without being directly aware of each other.
  • Global Module: A module located directly under src/ that provides shared, cross-cutting functionality for the entire application (e.g., ui/, utils/, services/).
  • Model (Domain Model): A pure, framework-agnostic software representation of a real-world business concept (e.g., a ShoppingCart class). It contains data and the business rules that apply to it.
  • Pattern Component: A global UI component from ui/patterns/ that is composed of several core components to solve a common, opinionated UI task (e.g., confirm-modal).
  • Vertical Slice Architecture: An architectural pattern where code is organized by feature (a “vertical slice”) rather than by technical layer (a “horizontal layer”).
  • View: A top-level presentational component, typically in domains/[feature-name]/views/. A route’s primary job is to render a view.

8. Summary of Directory Scopes

  • Feature-Scoped (within domains/[feature-name]/): All directories related to a feature’s operation (components/, logic/, views/, etc.).
  • Globally-Scoped (direct children of src/): ui/, router/, layouts/, assets/, styles/, logic/, utils/, services/, store/, and types/.
  • Project-Level (root): A docs/ directory for high-level documentation and a tests/ directory for E2E/integration tests.

9. Key Architectural Principles

  • Modularity: Domain-driven organization supports parallel development.
  • Reusability: Global directories promote DRY principles for common logic and UI.
  • Clear Separation: Explicit scopes for feature-specific, global, application, and domain logic minimize cognitive load.
  • Maintainability: Co-located tests and documentation improve code quality and longevity.
  • Framework Independence: These principles are adaptable to any modern frontend framework.
  • Team Autonomy: Clear boundaries empower teams to own features with reduced interference.

10. Representation

  • src/
    Root Directory
    • domains/
      Feature Modules
      Domain-Driven Architecture

      Each domain is a self-contained feature module following Domain-Driven Design principles. Think of them as vertical slices of your application that contain everything needed for a specific business capability.

      • shopping-cart/
        Primary Example
        • README.md
          Documentation
        • __tests__/
          Co-located Tests
          Test Co-location Principle

          Tests live in __tests__/ subdirectories next to the code they test. This improves discoverability and maintains clear relationships between implementation and validation.

          • cart.test.js
          • use-cart.test.js
        • shopping-cart-routes.js
          Config-based Routing
          Configuration-Based Routes

          For React Router, Vue Router, etc. Each domain exports its route configuration to be assembled by the central router.

        • logic/
          Business Logic
          Domain-Driven Design Layers

          Complex domains separate pure business logic (domain/) from framework-specific application logic (application/). This separation improves testability and maintainability.

          • domain/
            Pure Models
            • cart.js
              Domain Model
              Pure Business Logic

              Framework-agnostic classes with business rules. Example: Cart class with addItem(), calculateTotal() methods that enforce business constraints.

            • product.js
              Domain Model
            • __tests__/
              Model Tests
          • application/
            App Orchestration
            • use-cart.js
              React Hook
              Application Logic Layer

              Connects domain models to UI and infrastructure. Handles user input, data fetching, and coordinates between domain models and external services.

            • cart-controller.js
              Controller
            • __tests__/
              Hook Tests
        • components/
          Domain Components
          • cart-item.astro
          • cart-summary.astro
          • add-to-cart-button.astro
          • __tests__/
            Component Tests
        • views/
          Page Views
          View Components

          Top-level presentational components that routes render. They orchestrate data and compose smaller components to build complete user interfaces.

          • cart-view.astro
          • checkout-view.astro
          • __tests__/
            View Tests
        • translations/
          i18n Files
          • en.json
          • pt-br.json
          • es.json
        • types/
          TypeScript Types
          • cart.ts
          • product.ts
        • assets/
          Domain Assets
          Optional Asset Organization

          Asset subfolders are optional. Create them only when you have multiple assets of that type. Simple domains might place all assets directly in the assets/ folder.

          • images/
            Domain Images
            • empty-cart-illustration.svg
              Cart Illustration
            • product-placeholder.png
              Product Placeholder
          • icons/
            Domain Icons
            • cart-icon.svg
              Cart Icon
            • checkout-icons.svg
              Checkout Icons
        • utils/
          Domain Utilities
          • cart-helpers.js
          • price-formatters.js
      • user-profile/
        Secondary Example
        • README.md
          Documentation
        • __tests__/
          Co-located Tests
        • user-profile-routes.js
          Route Config
        • components/
          UI Components
          Domain-Specific Components

          Components here are domain-specific and not shared across features. If a component needs to be used elsewhere, promote it to the global ui/ directory.

          • profile-avatar.astro
          • settings-form.astro
          • profile-stats.astro
          • __tests__/
            Component Tests
        • views/
          Page Views
          • profile-view.astro
          • settings-view.astro
          • edit-profile-view.astro
        • logic/
          Business Logic
          • user-service.js
          • profile-validator.js
          • __tests__/
            Logic Tests
      • authentication/
        Domain Example
        Authentication as a Domain

        Authentication treated as a first-class business domain with its own vertical slice. Contains all auth-related UI, business logic, and state management rather than being scattered globally.

        • README.md
          Domain Documentation
        • __tests__/
          Co-located Tests
        • authentication-routes.js
          Auth Routes Config
        • components/
          Auth UI Components
          • login-form.astro
            Login Form
          • register-form.astro
            Registration
          • password-reset-form.astro
            Password Reset
          • auth-guard.astro
            Route Protection
          • __tests__/
            Component Tests
        • views/
          Auth Views
          • login-view.astro
            Login Page
          • register-view.astro
            Registration Page
          • forgot-password-view.astro
            Password Recovery
          • __tests__/
            View Tests
        • logic/
          Auth Business Logic
          • domain/
            Pure Auth Models
            • user.js
              Domain Model
              User Domain Model

              Pure User class with authentication business rules - password validation, role checking, session expiry logic. Framework-agnostic business behavior.

            • session.js
              Session Model
            • auth-token.js
              Token Model
            • __tests__/
              Domain Tests
          • application/
            Auth Orchestration
            • use-auth.js
              Auth Hook
              Authentication Orchestration

              React hook that coordinates auth domain models with UI and API. Handles login/logout flows, token management, and user state.

            • auth-controller.js
              Auth Controller
            • permission-manager.js
              Permission Logic
            • __tests__/
              Application Tests
        • store/
          Auth State
          • auth-store.js
            User Session
          • permission-store.js
            User Permissions
          • __tests__/
            Store Tests
        • types/
          Auth Types
          • user.ts
            User Interfaces
          • auth.ts
            Auth Interfaces
          • permissions.ts
            Permission Types
        • utils/
          Auth Utilities
          • token-helpers.js
            JWT Utils
          • password-validators.js
            Password Rules
          • auth-formatters.js
            Data Formatting
          • __tests__/
            Util Tests
        • translations/
          Auth i18n
          • en.json
            English Auth Text
          • pt-br.json
            Portuguese Auth Text
          • es.json
            Spanish Auth Text
        • assets/
          Auth Assets
          • login-bg.jpg
            Login Background
          • auth-icons.svg
            Auth Icons
      • product-catalog/
        Domain Example
      • order-management/
        Domain Example
      • ❌ ANTI-PATTERN: Direct Imports
        DON'T DO THIS
        Inter-Domain Import Anti-Pattern

        NEVER import directly between domains (e.g., shopping-cart importing from user-profile). This creates tight coupling. Instead, promote shared code to global directories or use the event bus for communication.

        • ❌ import { userService } from '../user-profile/logic'
          Tight Coupling
        • ✅ Use Event Bus Communication
          Decoupled
        • ✅ Promote to Global Directories
          Shared Code
    • ui/
      UI System
      Global UI System

      The global UI system with a three-tier structure: core (atomic components), patterns (composed solutions), and lab (experimental features). No barrel files (index.js) allowed.

      • core/
        Atomic Components
        Atomic, Abstract Building Blocks

        Atomic, context-free building blocks like buttons and inputs. These are the foundation of your design system - highly reusable and make no assumptions about their usage.

        • button.astro
          Foundational
        • input-field.astro
          Foundational
        • card.astro
          Layout
        • modal-shell.astro
          Overlay
        • badge.astro
          Indicator
        • spinner.astro
          Feedback
        • checkbox.astro
          Form Control
        • __tests__/
          Core Tests
      • patterns/
        Composed Components
        Composed UI Patterns

        Opinionated combinations of core components that solve common UI patterns. They trade flexibility for consistency and development speed.

        • user-avatar-with-status.astro
          User Pattern
        • product-card.astro
          E-commerce
        • confirm-modal.astro
          Confirmation
        • data-table.astro
          Data Display
        • search-with-filters.astro
          Search Pattern
        • notification-toast.astro
          Feedback
        • __tests__/
          Pattern Tests
      • lab/
        Experimental
        Experimental Components

        Safe space for experimental components and proof-of-concepts. Components must meet stability criteria before graduating to core or patterns.

        • virtual-scroll-list.astro
          Performance
        • ai-chat-interface.astro
          AI/ML
        • gesture-detector.astro
          Interaction
        • experimental-chart.astro
          Data Viz
        • __tests__/
          Experimental Tests
    • 🚦 ROUTING APPROACHES
      Two Strategies
      Choose Your Routing Strategy

      The architecture supports both configuration-based routing (React Router, Vue Router) and file-based routing (Next.js, SvelteKit, Astro). Choose based on your framework.

      • router/
        Config-based Routing
        Configuration-Based Routing

        For React Router, Vue Router, etc. Central router aggregates route configurations from each domain's [feature-name]-routes.js file.

        • app-router.js
          Central Router
          Central Route Assembly

          Imports and combines all domain route configurations into a single router instance. Each domain maintains its own routes file.

        • route-guards.js
          Auth Guards
        • router-config.js
          Global Config
        • __tests__/
          Router Tests
      • pages/
        File-based Routing
        File-Based Routing

        For Next.js, SvelteKit, Astro, etc. Filesystem structure defines URL structure. Keep route files lean - they should orchestrate, not implement.

        • index.astro
          Home Route
        • [slug].astro
          Dynamic Route
          Keep Routes Lean

          Route files should be thin orchestration layers. Import and render views from the appropriate domain directory.

        • about.astro
          Static Route
        • 404.astro
          Error Page
        • cart/
          Domain Routes
          • index.astro
            /cart
          • checkout.astro
            /cart/checkout
        • profile/
          Nested Routes
          • index.astro
            /profile
          • settings.astro
            /profile/settings
    • layouts/
      Global Structure
      Global Page Templates

      Reusable page structure templates that wrap your views. Domain-specific layouts should live within their respective domain directories.

      • main-layout.astro
        App Shell
      • auth-layout.astro
        Login/Register
      • admin-layout.astro
        Admin Panel
      • minimal-layout.astro
        Landing Pages
      • __tests__/
        Layout Tests
    • assets/
      Global Assets
      Global Static Resources

      Global static resources like images and fonts. Domain-specific assets should live in their respective domain/assets/ directories.

      • images/
        Global Images
        • logo.svg
          Brand Logo
        • hero-bg.jpg
          Hero Background
        • placeholder.png
          Global Placeholder
      • fonts/
        Typography
        • inter.woff2
          System Font
        • roboto-mono.woff2
          Code Font
      • icons/
        Icon System
        • sprite.svg
          SVG Sprite
        • favicon.ico
          Browser Icon
      • videos/
        Multimedia Content
        • hero-video.mp4
          Brand Video
        • background-loop.webm
          Background Video
      • documents/
        Global Documents
        • terms-of-service.pdf
          Legal Document
        • user-manual.pdf
          User Guide
    • styles/
      Global Stylesheets
      Global Styling System

      Global styling system including CSS resets, base element styles, design tokens, and theme variables. Component-specific styles should be co-located with their components.

      • global.css
        Global Styles
      • theme.css
        Design Tokens
      • reset.css
        CSS Reset
      • variables.css
        CSS Variables
      • typography.css
        Text Styles
    • utils/
      Pure Functions
      Global Utility Functions

      Stateless utility functions used across multiple domains. These should be pure functions with no side effects - formatters, validators, data transformers, etc.

      • formatters.js
        Data Formatting
      • validators.js
        Input Validation
      • date-utils.js
        Date Helpers
      • string-utils.js
        String Manipulation
      • array-helpers.js
        Array Operations
      • __tests__/
        Utility Tests
    • logic/
      Shared Business Logic
      Cross-Domain Business Logic

      Global business logic that's shared across multiple domains. This is different from utils - it contains stateful logic, complex business rules, and cross-cutting concerns.

      • shared-rules.js
        Business Rules
      • permissions-engine.js
        Access Control
      • business-constants.js
        Domain Constants
      • cross-domain-workflows.js
        Multi-Domain Logic
      • __tests__/
        Logic Tests
    • services/
      Infrastructure Services
      Cross-Cutting Infrastructure

      Global services for cross-cutting concerns and infrastructure interactions. Includes API clients, event bus, logging, and browser API wrappers.

      • http-client.js
        API Client
      • api-config.js
        API Configuration
      • event-bus.js
        Inter-Domain Communication
        Domain Communication Hub

        The event bus is the ONLY way domains should communicate with each other. It provides publish/subscribe functionality to maintain loose coupling between features.

      • logger.js
        Logging Service
      • browser-apis.js
        Browser Wrappers
      • __tests__/
        Service Tests
    • store/
      Global State
      Application-Wide State

      Global state management for truly application-wide concerns like theme, notifications, and global UI state. Domain-specific state should remain in domain stores.

      • app-store.js
        Core App State
      • theme-store.js
        Theme Management
      • notification-store.js
        Global Notifications
      • global-ui-state.js
        UI State
      • __tests__/
        Store Tests
    • types/
      Global Types
      Shared Type Definitions

      TypeScript type definitions used across multiple domains. Domain-specific types should remain in their respective domain/types/ directories.

      • api.ts
        API Interfaces
      • common.ts
        Shared Types
      • events.ts
        Event Bus Types
      • ui.ts
        UI Component Types
      • routing.ts
        Route Types
    • 📋 ARCHITECTURE SUMMARY
      Key Principles
      Framework-Agnostic Principles

      This architecture promotes modularity, clear separation of concerns, and framework independence through Vertical Slice Architecture principles adapted for frontend development.

      • ✅ High Cohesion
        Feature-Driven
      • ✅ Low Coupling
        Event Bus Communication
      • ✅ Test Co-location
        __tests__/ Everywhere
      • ✅ Framework Agnostic
        Adaptable Structure
      • ✅ Domain-Driven Design
        Pure Domain Models
      • ✅ Kebab-case File Names
        Consistent Naming