How to Structure a .NET + React Monorepo for Maintainability

As enterprise applications grow, so does the complexity of managing multiple codebases—one for your .NET backend, another for your React frontend, and often shared libraries in between.

That’s where the monorepo shines. By keeping both the frontend and backend in a single repository, you streamline collaboration, dependency management, and CI/CD pipelines.

But without the right structure, a monorepo can quickly turn into a tangled mess. This guide walks you through how to architect a .NET + React monorepo that stays clean, modular, and scalable over time.


1. Why a Monorepo for .NET + React?

Before diving into folder structures, let’s clarify the benefits.

Advantages:

  • Unified version control: One repo, one source of truth.
  • Simplified CI/CD: Single pipeline for testing, building, and deployment.
  • Shared code: Common DTOs, utilities, and types between frontend and backend.
  • Synchronized updates: Frontend and API changes evolve together.

For teams building enterprise web apps with APIs and / frontends, the monorepo approach reduces friction dramatically.


2. Recommended Folder Structure

Here’s a clean, scalable layout for your monorepo:

/src
  /client/                # React or Next.js frontend
  /server/                # ASP.NET Core backend (Web API)
  /shared/                # Reusable code, models, and utilities
/tests
  /client-tests/
  /server-tests/
  /integration-tests/
.gitignore
README.md
package.json
global.json

Key Design Principles

  • Keep client and server isolated but interoperable.
  • The shared folder contains cross-cutting concerns—like DTOs, validation schemas, and OpenAPI-generated clients.
  • Use consistent naming and clear boundaries for every layer.

3. Synchronize Contracts Between .NET and React

One of the biggest pain points in full-stack development is keeping frontend models aligned with backend DTOs.

Best Practice: Generate TypeScript Types Automatically

  • Use OpenAPI (Swagger) from your ASP.NET backend.
  • Generate TypeScript models using tools like: npx openapi-typescript https://localhost:5001/swagger/v1/swagger.json -o src/shared/types/api.ts

This ensures the frontend always has up-to-date, type-safe API models—no manual syncing required.


4. Manage Dependencies with the Right Tools

In a monorepo, dependency management can get messy fast.

Recommended Setup:

  • Use npm workspaces or pnpm workspaces for the React side.
  • Keep the .NET backend isolated from node_modules—no shared dependency tree.
  • Maintain separate lock files for frontend (package-lock.json) and backend (dotnet restore).

Example package.json snippet for the root workspace:

{
  "private": true,
  "workspaces": ["src/client"]
}

This gives you modular builds while maintaining cross-project coordination.


5. Implement Consistent Build and Run Scripts

Your monorepo should provide a consistent developer experience:

Root-Level Scripts

Add unified commands in your root package.json or .sln file:

# Build both projects
npm run build:all

# Run both projects concurrently
npm run dev

Use tools like concurrently or npm-run-all to start both servers:

"dev": "concurrently \"dotnet watch run --project src/server\" \"npm start --prefix src/client\""

This keeps onboarding frictionless for new developers.


6. Share Code Between Projects

In .NET

Move shared logic (e.g., validators, constants) to a Class Library project:

/src
  /server/
  /shared/
    Shared.Core/
      Shared.Core.csproj

Reference it in your Web API project:

dotnet add src/server reference src/shared/Shared.Core

In React

Expose shared frontend utilities and types via a local import:

import { ApiResponse } from '@shared/types/api';

This makes it clear where shared code lives and encourages reusability.


7. Enforce Code Quality and Standards

Consistency is the secret weapon of maintainability.

Recommended Tools

  • ESLint + Prettier: Enforce consistent style and imports in React.
  • StyleCop / Roslyn Analyzers: Maintain consistent C# standards.
  • Husky + lint-staged: Run linters before commits.
  • EditorConfig: Keep formatting unified across IDEs.

All these should run from the root of the monorepo to enforce global consistency.


8. Automate Testing Across Projects

Testing is critical for monorepos, where changes in one module can impact another.

Testing Setup

  • Unit tests: xUnit for .NET, Jest or Vitest for React.
  • Integration tests: Test shared contracts (e.g., API endpoints).
  • CI/CD: Run both suites in one pipeline—fail fast if either breaks.

Example GitHub Actions YAML snippet:

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Build and Test .NET
        run: dotnet test src/server
      - name: Test React
        run: npm test --prefix src/client


9. Optimize CI/CD for Incremental Builds

Large monorepos can slow down pipelines if you’re not careful.

Best Practices

  • Use path filters in CI—build only changed projects.
  • Cache dependencies (NuGet, npm) between runs.
  • Deploy backend and frontend separately if needed (e.g., Azure App Service + Vercel).

This approach keeps pipelines efficient while preserving integration integrity.


10. Document Everything

Finally, don’t let tribal knowledge become your bottleneck.
Include a top-level README.md with:

  • Setup instructions
  • Environment variable documentation
  • Local run commands
  • Deployment process overview

Good documentation reduces friction as your team scales.


Conclusion

A well-structured .NET + React monorepo brings enormous benefits: simplified coordination, shared contracts, and streamlined deployment.

By following best practices—like modular folder organization, automated type generation, consistent tooling, and efficient CI/CD—you can keep your codebase clean and maintainable for years.