Modern enterprise platforms often contain multiple applications that serve different business capabilities.

At first, maintaining these applications as completely independent projects may seem straightforward. Over time, however, teams often discover that the applications share a significant amount of functionality: authentication, UI components, API clients, validation, utilities, configuration, types, and common business behaviour.

One approach to solving this problem is a frontend monorepo.

In one of the enterprise applications I worked on, multiple React applications supporting different business capabilities were maintained within a single Git repository. These included applications such as:

Although these applications belonged to the same overall platform, they remained separate applications with their own functional responsibilities.

The monorepo approach allowed us to maintain these applications together while sharing common frontend capabilities.

This article explains the architecture, the reasoning behind the approach, its benefits and challenges, and some of the lessons learned from implementing a multi-application React monorepo.

1. What Is a Monorepo?

A monorepo, or monolithic repository, is a source-control strategy where multiple applications and libraries are maintained in a single repository.

Without a monorepo, we could have:

agreement-ui
 nominations-ui
 cargo-ops-ui
 deal-check-ui
 admin-ui
 shared-ui

Each application could have its own Git repository.

With a monorepo, these projects are brought together:

enterprise-frontend/
│
├── apps/
│   ├── agreements/
│   ├── nominations/
│   ├── cargo-operations/
│   ├── deal-check/
│   ├── administration/
│   └── feature-management/
│
├── packages/
│   ├── ui-components/
│   ├── authentication/
│   ├── api-client/
│   ├── shared-types/
│   ├── utilities/
│   └── configuration/
│
├── package.json
├── configuration/
└── README.md

The important point is that one repository does not mean one application.

A monorepo can contain multiple independently deployable applications.

That distinction is particularly important in enterprise architecture.

2. Monorepo Does Not Mean Monolith

The terms are sometimes confused.

A monolithic application might look like:

                 One Application
                      |
       +--------------+------------------+
       |              |              |
   Agreements    Nominations      Cargo Ops
       |              |              |
       +--------------+------------------+
                      |
                   Database

Everything is part of the same application and generally shares the same deployment lifecycle.

A frontend monorepo is different:

                 Git Monorepo
                      |
       +--------------+------------------+
       |              |              |
       ▼              ▼              ▼
 Agreements      Nominations     Cargo Ops
    App              App            App
       |              |              |
       ▼              ▼              ▼
   Deployable      Deployable     Deployable
   Application     Application    Application

The repository is shared, but the applications can remain logically and operationally separate.

This gives us an important architectural principle:

Repository boundaries and application boundaries do not have to be the same thing.

3. Why Use a Monorepo for Multiple React Applications?

Consider an enterprise platform with several React applications.

Each application may require:

Authentication
Authorization
Header
Navigation
Buttons
Dialogs
Data Grid
Notifications
API communication
Validation
Logging
Common utilities

With separate repositories, these capabilities can become duplicated.

For example:

Agreement Repository
    ├── Login
    ├── Button
    ├── DataGrid
    └── API Client

Nominations Repository
    ├── Login
    ├── Button
    ├── DataGrid
    └── API Client

Cargo Repository
    ├── Login
    ├── Button
    ├── DataGrid
    └── API Client

This creates several problems.

A bug fixed in one implementation may need to be fixed in several others.

A UI change may have to be replicated across repositories.

Different teams may use different versions of the same library.

Over time, the applications can start behaving differently.

A monorepo provides another option:

                    Shared Libraries
                          |
          +-----------------+-----------------+
          |               |               |
          ▼               ▼               ▼
      Agreements      Nominations      Cargo Ops

Common functionality can be implemented once and consumed by multiple applications.

4. High-Level Architecture

A simplified version of the frontend architecture can be represented as follows:

                         React Monorepo
                              |
       +--------------------------+-------------------------+
       |                      |                      |
       ▼                      ▼                      ▼
+----------------+                        +----------------+                      +-----------------+
| Agreements   |       | Nominations  |       | Cargo Ops    |
| React App    |       | React App    |       | React App    |
+------+---------+                      +---------+-------+                     +---------+-------+
       |                      |                      |
       +--------------------------+--------------------------+
                              |
                    Shared Frontend Libraries
                              |
          +----------------------+----------------------+
          |                   |                   |
          ▼                   ▼                   ▼
     UI Components      Authentication       API Client
          |                   |                   |
          +----------------------+----------------------+
                              |
                              ▼
                         API Gateway
                              |
              +-----------------+-----------------+
              |               |               |
              ▼               ▼               ▼
        Agreement MS    Nomination MS    Cargo Ops MS

This architecture gives us two important characteristics:

Shared development model

Applications and common libraries live together.

Independent application boundaries

Each application still represents a specific business capability.

5. Applications and Shared Libraries

One of the most important design decisions in a monorepo is separating applications from reusable libraries.

A conceptual structure could be:

apps/
    agreements/
    nominations/
    cargo-operations/
    deal-check/

packages/
    ui/
    authentication/
    api-client/
    models/
    utilities/

The apps directory contains executable applications.

The packages directory contains reusable libraries.

For example:

apps/agreements
       |
       +----> packages/ui
       |
       +----> packages/authentication
       |
       +----> packages/api-client
       |
       +----> packages/utilities

The Nominations application can use the same libraries:

apps/nominations
       |
       +----> packages/ui
       |
       +----> packages/authentication
       |
       +----> packages/api-client
       |
       +----> packages/utilities

This is one of the major benefits of the monorepo approach.


6. Shared UI Components

Enterprise applications generally contain many common UI components.

For example:

packages/ui/

    Button
    Modal
    Dialog
    DataGrid
    DatePicker
    Dropdown
    Notification
    LoadingIndicator
    FormControls

Instead of every application implementing its own version of these components, they can consume a common library.

For example:

import { Button } from '@platform/ui';

<Button
    variant="primary"
    onClick={handleSave}>
    Save
</Button>

The Agreements application and Cargo Operations application can use the same component.

This provides consistency in:

A change to a shared component can then be propagated to the applications that consume it.

7. Avoiding the "Shared Everything" Problem

There is an important warning here.

Just because code can be shared does not mean it should be shared.

For example, imagine:

packages/shared/
    agreementLogic
    nominationLogic
    cargoLogic
    dealLogic
    adminLogic
    ...

Eventually, the shared package can become a dumping ground for unrelated functionality.

Instead, shared code should have a clear purpose.

A useful rule is:

Share technical capabilities aggressively, but share business logic deliberately.

Good candidates for shared libraries:

UI components
Authentication
HTTP client
Logging
Date utilities
Common validation
Configuration
Type definitions

Business-specific logic should generally remain within the relevant application unless there is a genuine common domain requirement.

8. Shared Authentication

Authentication is another strong candidate for reuse.

Suppose every application needs to understand:

Login
Token handling
Token refresh
Logout
Session management
Authorization
Route protection

Implementing these separately creates unnecessary duplication.

Instead:

packages/authentication

can provide common capabilities.

Conceptually:

                  Authentication Library
                           |
          +------------------+-------------------+
          |                |                |
          ▼                ▼                ▼
      Agreements      Nominations       Cargo Ops

Each application consumes the same authentication behaviour.

This also provides a central place to address changes to authentication mechanisms.

For example, if the identity provider changes, the implementation can potentially be updated in one shared location rather than separately across every application.

9. Authorization and Route Protection

Authentication answers:

Who is the user?

Authorization answers:

What is the user allowed to do?

Different business applications may expose different permissions.

For example:

User
 |
 +-- Agreements
 |       +-- View
 |       +-- Edit
 |       +-- Approve
 |
 +-- Nominations
 |       +-- View
 |       +-- Create
 |
 +-- Cargo Operations
         +-- View
         +-- Manage

A shared authorization mechanism can provide common permission handling while individual applications determine which permissions are relevant to their functionality.

A conceptual React route might look like:

<ProtectedRoute
    permission="Agreement.Edit">
    
    <AgreementEditPage />

</ProtectedRoute>

The actual implementation can vary depending on the authentication and authorization mechanism used.

10. Shared API Client

Another important shared capability is communication with backend APIs.

Without a common API client, every application might independently implement:

HTTP requests
Headers
Authentication tokens
Error handling
Timeouts
Retries
Correlation IDs
Logging

A shared client can centralize these concerns.

                  Shared API Client
                         |
       +--------------------+--------------------+
       |                 |                 |
       ▼                ▼                 ▼
 Agreements API   Nominations API    Cargo API

For example:

const response =
    await apiClient.get<AgreementContract>(
        `/agreements/${agreementId}`
    );

The client can handle cross-cutting concerns without forcing every page to implement them repeatedly.

11. Contract-Driven Frontend Integration

This monorepo architecture becomes even more interesting when combined with the contract-driven API approach discussed in the first article.

The complete flow becomes:

React Application
       |
       v
Shared API Client
       |
       v
API Gateway
       |
       v
.NET Microservice
       |
       v
Contract / DTO
       |
       v
React Application

For example:

Agreement Application
        |
        | GET
        v
API Gateway
        |
        v
Agreement Service
        |
        v
AgreementContract
        |
        v
React State

The user modifies the data.

During save:

React State
     |
     v
AgreementContract
     |
     v
Shared API Client
     |
     v
API Gateway
     |
     v
Agreement Service
     |
     v
EF Core
     |
     v
SQL Server

This creates a clear frontend/backend boundary.

12. Shared TypeScript Contracts

If the backend exposes well-defined contracts, frontend TypeScript models can represent them.

For example:

export interface AgreementContract {
    agreementId: number;
    agreementNumber: string;
    status: string;
    effectiveDate: string;
    expiryDate?: string;
    rowVersion: string;
}

The model can be consumed by the Agreements application:

import {
    AgreementContract
} from '@platform/models';

The benefit is consistency.

Instead of defining the same contract differently in several applications:

Agreement App
AgreementContract

Admin App
AgreementContract

Reporting App
AgreementContract

a common representation can be maintained where appropriate.

However, shared contracts should be managed carefully.

If an application has different requirements from another application, forcing both to consume one giant model can create unnecessary coupling.

13. Dependency Graph

One of the most useful concepts in a monorepo is the dependency graph.

Consider:

                     UI Library
                         |
            +--------------+--------------+
            |            |            |
            ▼            ▼            ▼
       Agreements   Nominations    Cargo Ops
            |            |            |
            +--------------+--------------+
                         |
                   API Client
                         |
                  Authentication

The applications depend on common libraries.

If the UI library changes, the monorepo tooling can determine which applications are affected.

Conceptually:

Change:

packages/ui/Button

        |
        v

Affected Projects:

Agreements
Nominations
Cargo Operations
Deal Check

This becomes particularly valuable as the repository grows.

14. Independent Application Development

Although applications share a repository, developers can focus on their respective business areas.

For example:

Team A
    Agreements

Team B
    Nominations

Team C
    Cargo Operations

Team D
    Deal Check

Platform Team
    Shared UI
    Authentication
    API Client

The repository provides a common development environment while application ownership remains clear.

This is one reason why defining ownership is important in a large monorepo.

15. Independent Builds

A common misconception is that a monorepo means the entire repository must be built whenever anything changes.

That doesn't have to be the case.

A mature monorepo setup can identify affected applications.

For example:

Developer changes:

apps/agreements/

The CI/CD system can potentially execute:

Build Agreements
       |
       v
Test Agreements
       |
       v
Deploy Agreements

rather than:

Build Everything
       |
       v
Test Everything
       |
       v
Deploy Everything

This becomes increasingly important as the number of applications grows.

The exact implementation depends on the monorepo tooling and CI/CD pipeline.

16. CI/CD Pipeline for a Monorepo

The CI/CD pipeline needs to understand that the repository contains multiple applications.

A conceptual pipeline is:

                       Git Commit
                           |
                           v
                    Azure DevOps
                           |
                           v
                    Change Detection
                           |
             +---------------+---------------+
             |             |             |
             v             v             v
         Agreement      Cargo Ops     Nominations
             |             |             |
             v             v             v
           Build         Build         Build
             |             |             |
             v             v             v
           Test          Test          Test
             |             |             |
             v             v             v
         Package        Package       Package
             |             |             |
             v             v             v
         Deployment     Deployment    Deployment

This provides an important advantage:

A single repository can still support independent application delivery.

17. What Happens When a Shared Library Changes?

This is one of the more interesting scenarios in a monorepo.

Suppose:

packages/ui/DataGrid

is changed.

The dependency graph might identify:

DataGrid
   |
   +--> Agreements
   |
   +--> Nominations
   |
   +--> Cargo Operations

Those applications may need to be rebuilt and tested.

On the other hand, if a change is completely isolated to:

apps/agreements/

then unrelated applications don't necessarily need to be rebuilt.

This is where good dependency management and affected-project detection become important.


18. Git Workflow

A monorepo also changes how teams think about Git.

Instead of:

Agreement Repository
Nominations Repository
Cargo Repository

developers work in:

Enterprise Frontend Repository

A feature branch might contain:

feature/agreement-validation

with changes such as:

apps/agreements/...
packages/ui/...
packages/models/...

A single pull request can therefore contain coordinated changes across an application and its shared dependencies.

For example:

PR

Agreement Page
     +
Shared DatePicker
     +
Agreement Contract

This can be much easier than coordinating changes across multiple repositories.

19. Cross-Application Refactoring

This is one of the strongest arguments for a monorepo.

Suppose an organization decides to change the standard API error-handling mechanism.

With separate repositories:

Agreement Repo
       ↓
PR

Nominations Repo
       ↓
PR

Cargo Repo
       ↓
PR

Deal Check Repo
       ↓
PR

With a monorepo:

Shared API Client

       ↓

One coordinated change

       ↓

Affected applications

The complete change can be reviewed in one place.

This is particularly useful for enterprise-wide technical changes.

20. Code Consistency

A monorepo also provides an opportunity to standardize:

ESLint
Prettier
TypeScript
Testing
Build configuration
Dependency versions
Coding standards
Git hooks

For example, applications can share common configuration:

configuration/
    eslint
    typescript
    prettier
    testing

This reduces the possibility of different applications gradually developing different technical standards.

21. Dependency Management

Dependency management becomes particularly important in a monorepo.

Suppose one application uses:

React version X

while another uses:

React version Y

and the shared UI library expects:

React version Z

This can create compatibility problems.

A monorepo provides centralized visibility into dependencies.

Teams can establish policies around:

The goal is not necessarily to force every package to have exactly the same dependency versions, but to make dependency relationships visible and manageable.

22. Testing Strategy

Testing can also be organized around applications and shared libraries.

For example:

packages/ui
    |
    +-- Unit Tests

packages/api-client
    |
    +-- Unit Tests

apps/agreements
    |
    +-- Unit Tests
    +-- Component Tests
    +-- Integration Tests

apps/cargo-operations
    |
    +-- Unit Tests
    +-- Component Tests
    +-- Integration Tests

The pipeline can then execute the appropriate tests based on what has changed.

Shared libraries deserve particularly strong test coverage because one defect can potentially affect multiple applications.

23. Monorepo and Microservices Can Coexist

Another important architectural observation from this type of system is that frontend repository organization and backend service architecture are independent decisions.

For example:

                    FRONTEND

                  Git Monorepo
                       |
       +---------------+---------------+
       |               |               |
       ▼               ▼               ▼
   Agreements     Nominations      Cargo Ops
     React           React           React


                    BACKEND

                  Microservices
                       |
       +---------------+---------------+
       |               |               |
       ▼               ▼               ▼
   Agreement       Nomination       Cargo Ops
    Service          Service          Service

The frontend may use a monorepo while the backend uses independently deployed microservices.

There is no contradiction.

The monorepo is primarily a source-code organization and development strategy.

Microservices are primarily an application architecture and deployment strategy.

They address different problems.

24. Monorepo vs Polyrepo

It is useful to compare the two approaches.

Area

Monorepo

Polyrepo

Source control

Single repository

Multiple repositories

Code sharing

Straightforward

Requires packages or duplication

Cross-application refactoring

Easier

More coordination required

Dependency visibility

Centralized

Distributed

CI/CD

More sophisticated

Usually simpler per repository

Repository size

Larger

Smaller

Team isolation

Lower

Higher

Shared standards

Easier to enforce

Can diverge

Cross-project changes

One PR possible

Multiple PRs often required

Build optimization

Requires affected-project strategy

Naturally isolated

Neither approach is universally better.

The right choice depends on organizational structure, application boundaries, release requirements, team autonomy, and the amount of shared functionality.

25. Challenges of a React Monorepo

A monorepo solves several problems, but it introduces its own challenges.

Repository Size

As applications and libraries increase, the repository can become large.

This requires good organization and tooling.

Build Performance

Building every application for every commit defeats one of the advantages of independent applications.

Affected-project builds become important.

Shared Library Coupling

A poorly designed shared library can create strong dependencies between applications.

For example:

Cargo
   |
   v
Shared Library
   ^
   |
Agreements

A seemingly small change can unexpectedly affect multiple applications.

Ownership

Someone needs to own shared libraries.

Otherwise, every team may change common code without understanding its impact.

Dependency Management

Common dependencies must be managed carefully.

CI/CD Complexity

The pipeline needs to understand multiple applications, shared libraries and affected projects.

26. Avoiding a Distributed Monolith

A poorly designed monorepo can gradually become what I would call a distributed frontend monolith.

For example:

Agreement
    |
    +----> Shared Library A
              |
              +----> Shared Library B
                        |
                        +----> Cargo
                                  |
                                  +----> Shared Library C

Now changing one application can potentially affect everything.

The purpose of a monorepo is not to remove boundaries.

It is to make collaboration and reuse easier while preserving appropriate boundaries.

Good architecture therefore requires:

27. Recommended Dependency Direction

A useful dependency structure is:

Applications
     |
     v
Shared Technical Libraries
     |
     v
Infrastructure / Utilities

For example:

Agreements
    |
    +--> UI
    +--> Authentication
    +--> API Client
    +--> Utilities

But ideally:

Shared UI
    X
    |
    X----> Agreements

The shared library should not depend on a specific business application.

Otherwise, the supposedly shared library becomes coupled to that application.

28. The Role of API Contracts

The frontend monorepo becomes particularly powerful when API contracts are treated as first-class interfaces.

For example:

                 Backend
                    |
                    v
            AgreementContract
                    |
                    v
               API Gateway
                    |
                    v
             Shared API Client
                    |
                    v
             Agreements App

The contract forms a boundary between frontend and backend.

This means the architecture has multiple boundaries:

Git Repository Boundary
        |
        v
Application Boundary
        |
        v
API Contract Boundary
        |
        v
Microservice Boundary
        |
        v
Data Boundary

Good architecture is largely about defining and protecting these boundaries.

29. When Should You Choose a Monorepo?

A monorepo is particularly attractive when multiple applications:

A polyrepo approach may be more appropriate when:

Again, there is no universal answer.

30. Lessons Learned

From an enterprise frontend perspective, several lessons stand out.

1. A monorepo is not a substitute for architecture

Putting everything in one Git repository does not automatically create good architecture.

The applications and libraries still need clear boundaries.

2. Shared code needs ownership

Shared libraries are effectively internal platforms.

They need maintainers, standards and controlled evolution.

3. Don't share business logic unnecessarily

Common UI and technical infrastructure are good candidates for reuse.

Business-specific functionality should remain close to the application that owns it unless there is a genuine common requirement.

4. Contracts are important

The API contract should be treated as an interface between the frontend and backend, not merely as JSON returned by an endpoint.

5. Build only what is affected

As the repository grows, affected-project detection becomes critical for keeping CI/CD efficient.

6. Independent deployment is still possible

Multiple applications can live in one repository while maintaining separate build and deployment lifecycles.

7. Monorepo and microservices solve different problems

A monorepo organizes source code and collaboration.

Microservices organize backend business capabilities and deployment boundaries.

They can complement each other very effectively.

31. Putting Everything Together

The overall architecture can therefore be represented as:

ChatGPT Image Sep 2, 2026, 04_08_32 PM

This architecture combines a frontend monorepo with a backend microservices architecture.

The repository provides a common development and collaboration model, while the application and service boundaries preserve separation of responsibilities.

Conclusion

Managing multiple enterprise React applications can become challenging when each application independently implements the same infrastructure, UI components, authentication mechanisms and API communication.

A frontend monorepo provides an alternative approach.

By bringing related applications into a single Git repository and organizing reusable functionality into shared libraries, teams can improve code reuse, consistency, cross-application refactoring and dependency visibility.

However, a monorepo should not become a reason to eliminate application boundaries.

The real objective is to achieve the right balance:

One repository for collaboration, multiple applications for business boundaries, and shared libraries for genuinely common capabilities.

In an enterprise platform, this can provide a strong foundation for multiple React applications while still allowing each business capability to evolve and deploy independently.

The combination of a React monorepo on the frontend and microservices on the backend is particularly powerful because each architecture addresses a different concern.

The monorepo optimizes how teams build and maintain the frontend.

The microservice architecture defines how the backend organizes business capabilities and services.

Together, they provide a scalable foundation for large enterprise applications.