Architecture

April 29, 2026 · View on GitHub

Core Technologies

  • React 19 with TypeScript
  • Vite 7 for build tooling
  • React Query (TanStack Query) for data fetching
  • React Router 7 for routing
  • i18next for internationalization
  • MSW 2 for API mocking
  • Vitest 4 for unit and component testing
  • Storybook 10 for component development
  • Playwright for E2E testing
  • Zustand for state management
  • Chakra UI for components

Project Structure

.
├── e2e/           # End-to-end tests with Playwright
└── src/
    ├── app/           # App-level configuration (App.tsx, Providers.tsx)
    ├── features/      # Feature modules using feature slice architecture
    │   ├── auth/      # Authentication feature (cross-cutting concern)
    │   ├── carts/     # Shopping cart feature
    │   ├── products/  # Product catalog feature
    │   └── marketing/          # Feature with sub-feature slices
    │       ├── components/     # Marketing-wide components
    │       ├── providers/      # Marketing-wide providers
    │       ├── models/         # Marketing-wide models
    │       ├── rating/         # Sub-feature slice
    │       │   ├── components/
    │       │   ├── application/
    │       │   ├── providers/
    │       │   └── models/
    │       └── reviews/        # Sub-feature slice
    │           ├── components/
    │           ├── application/
    │           ├── providers/
    │           └── models/
    ├── lib/           # Shared libraries and utilities
    │   ├── api/       # Centralized API layer (queries, mutations, DTOs)
    │   ├── components/ # Reusable UI components
    │   ├── http/      # HTTP client and error handling
    │   ├── i18n/      # Internationalization setup
    │   ├── router/    # Routing utilities
    │   └── theme/     # Theme configuration
    ├── pages/         # Route-level page components composing feature components & logic
    └── test-lib/      # Testing utilities and fixtures

Feature Architecture

Each feature follows feature slice architecture patterns with four layers:

  • components/ - UI components, presentational and decoupled from business logic (application) and router state. Data access is only through providers/.
  • application/ - Business logic, portable state management (stores, FSMs, form validation), custom hooks. Should not depend on router state or external APIs directly (only through providers/).
  • providers/ - Hook composition and data access gateway for the feature slice. Exposes query hooks, mutations, loaders, and domain errors sourced from src/lib/api/. When the DTO shape diverges from the domain model, the mapping function lives here — applied inside the query/mutation hook so consumers always receive the correct domain model type.
    • files are named after their primary export or reexport: useCartProductsQueryuse-cart-products-query.ts, useAddToCartMutationuse-add-to-cart-mutation.ts
  • models/ - Domain type definitions only. Exposes frontend models for the feature. When the DTO shape is identical to the domain model, re-export with a domain name (export type { ProductDto as Product }). When it diverges, define the domain type here.

Dependency rule

LayerMay import from
components/application/, providers/, models/, lib/*
application/providers/, models/, lib/*
providers/models/, lib/api/, lib/*
models/lib/api/, lib/*

Cross-slice primitives: features/auth/ and features/authv2/ are cross-cutting concerns (identity, permissions, auth state). Any feature slice may import from them.

Sub-feature Slices

When a feature grows to contain multiple distinct domain sub-areas, each sub-area becomes a sub-feature slice — a nested directory with its own four-layer structure (components/, application/, providers/, models/).

The parent feature's layers hold code that is either reusable across sub-feature slices, or too small to warrant its own sub-feature slice.

The same layer dependency rules apply within sub-feature slices. Additionally, sub-feature layers may import from the parent feature's same or lower layers. Sub-feature slices may not import from sibling sub-feature slices.

API Library

src/lib/api/ is the global home for all HTTP logic: queryOptions factories, loaders, mutation hooks, query keys, domain errors, and DTOs, organised by resource. Query files expose queryOptions factories (no useQuery hooks — hook composition belongs in providers/). Feature providers/ compose hooks on top of those factories and re-export them for feature slice. New API logic always goes in src/lib/api/ first, then gets exposed through the relevant feature's providers/.

File naming in src/lib/api/:

TypeSuffixExample
Query options-query.tscart-products-query.ts
Mutation options-mutation.tsadd-to-cart-mutation.ts
DTO interface-dto.tscart-product-dto.ts
Query keys-query-keys.tscart-query-keys.ts

Key Patterns

PatternDescription
Co-locationRelated files (component + story + test) grouped together
MSW handlersAPI mocking centralized in test-lib/handlers/
Fixture patternTest data generation in test-lib/fixtures/
Strong typingComprehensive TypeScript with branded types
Component CompositionFeatures export composed components for pages
Centralized APIAll API logic in src/lib/api/ with endpoint-based organization

State Management

TypeUse Case
XStateState orchestration with explicit states and constrained transitions (e.g., auth flows, multi-step processes)
Zustand storesComplex local state (auth, modals, etc.)
React QueryServer state and caching
React stateSimple component state

XState is preferred for business processes where states must be explicit and transitions constrained.

Internationalization

  • Translation files live in public/locales/{lang}/translation.json
  • Keys mirror the feature path e.g. features.products.<key>
  • All user-facing text goes through i18next — no hardcoded strings in components
  • When adding any value that renders as a user-facing label, add the corresponding translation key in the same task
  • When removing a value, remove its stale translation key

Routing

  • File-based routing - Pages in src/pages/ with corresponding loaders
  • Strong typing - Route paths defined in lib/router/routes.ts
  • Lazy loading - Components loaded on demand with error boundaries

Error Handling

  • Using react-error-boundary for unexpected component runtime errors

Build Optimization

  • Lazy loading and code splitting based on react-router
  • Using direct imports instead of default exports