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
orVitest
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.