17 Monorepo React (vite) + DotNet
I’ll help you design a mono-repo structure for a React SPA frontend with a .NET Web API backend. This is a great architecture choice where your .NET API can serve both the static SPA files and API endpoints.
17.1 Recommended Mono-Repo Structure
my-app/
│
├── frontend/ # React TypeScript SPA
│ ├── src/
│ │ ├── components/ # Reusable UI components
│ │ ├── pages/ # Page components
│ │ ├── services/ # API client services
│ │ ├── hooks/ # Custom React hooks
│ │ ├── utils/ # Utility functions
│ │ ├── types/ # TypeScript type definitions
│ │ ├── assets/ # Images, fonts, etc.
│ │ ├── App.tsx
│ │ ├── main.tsx # Entry point
│ │ └── index.css
│ │
│ ├── public/ # Static assets
│ ├── dist/ # Build output (ignored in git)
│ ├── package.json
│ ├── tsconfig.json
│ ├── vite.config.ts # Using Vite for build
│ ├── .env.development
│ └── .env.production
│
├── backend/ # .NET Web API
│ ├── MyApp.Api/ # Main API project
│ │ ├── Controllers/
│ │ ├── Models/
│ │ ├── Services/
│ │ ├── Data/ # DB context, repositories
│ │ ├── Middleware/
│ │ ├── Program.cs
│ │ ├── appsettings.json
│ │ ├── appsettings.Development.json
│ │ ├── MyApp.Api.csproj
│ │ └── wwwroot/ # Frontend build output goes here
│ │
│ ├── MyApp.Core/ # Domain models, interfaces
│ │ ├── Entities/
│ │ ├── Interfaces/
│ │ └── MyApp.Core.csproj
│ │
│ ├── MyApp.Infrastructure/ # Data access, external services
│ │ ├── Data/
│ │ ├── Repositories/
│ │ └── MyApp.Infrastructure.csproj
│ │
│ └── MyApp.sln # Solution file
│
├── scripts/ # Build and deployment scripts
│ ├── build.sh # Full build script
│ ├── dev.sh # Development startup
│ └── deploy.sh # Deployment script
│
├── docs/ # Project documentation
├── .gitignore
├── README.md
└── package.json # Root package.json for npm scripts
17.2 Key Configuration Files
17.2.1 1. Root package.json
(Orchestration)
{
"name": "my-app-monorepo",
"private": true,
"scripts": {
"dev": "concurrently \"npm run dev:frontend\" \"npm run dev:backend\"",
"dev:frontend": "cd frontend && npm run dev",
"dev:backend": "cd backend/MyApp.Api && dotnet watch run",
"build": "npm run build:frontend && npm run build:backend",
"build:frontend": "cd frontend && npm run build && npm run copy-to-backend",
"build:backend": "cd backend && dotnet build",
"clean": "rm -rf frontend/dist backend/MyApp.Api/wwwroot/*"
},
"devDependencies": {
"concurrently": "^8.2.0"
}
}
17.2.2 2. Frontend package.json
(Add copy script)
{
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"copy-to-backend": "rm -rf ../backend/MyApp.Api/wwwroot/* && cp -r dist/* ../backend/MyApp.Api/wwwroot/"
}
}
17.2.3 3. Backend Program.cs
(Serve SPA)
var builder = WebApplication.CreateBuilder(args);
// Add services
.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder
var app = builder.Build();
// Configure pipeline
if (app.Environment.IsDevelopment())
{
.UseSwagger();
app.UseSwaggerUI();
app}
.UseHttpsRedirection();
app
// Serve static files (React SPA)
.UseDefaultFiles();
app.UseStaticFiles();
app
// API routes
.MapControllers();
app
// Fallback to index.html for SPA routing
.MapFallbackToFile("index.html");
app
.Run(); app
17.3 Development Workflow
┌───────────────────────────────────────────────────── ┐
│ Development Mode │
├───────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Frontend │ :3000 │ Backend │ :5000 │
│ │ (Vite) │ ──────> │ (.NET API) │ │
│ └──────────────┘ proxy └──────────────┘ │
│ │
└───────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Production Build │
├─────────────────────────────────────────────────────┤
│ │
│ 1. Build React → dist/ │
│ 2. Copy dist/* → backend/wwwroot/ │
│ 3. .NET serves both API + SPA from single port │
│ │
└─────────────────────────────────────────────────────┘
17.4 Frontend Vite Configuration
// frontend/vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
: [react()],
plugins: {
server: 3000,
port: {
proxy'/api': {
: 'http://localhost:5000',
target: true
changeOrigin
}
},
}: {
build: 'dist',
outDir: 'assets',
assetsDir: false
sourcemap
} })
17.5 API Service Example (Frontend)
// frontend/src/services/api.ts
const API_BASE_URL = import.meta.env.DEV
? 'http://localhost:5000/api'
: '/api';
export class ApiService {
static async get<T>(endpoint: string): Promise<T> {
const response = await fetch(`${API_BASE_URL}${endpoint}`);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return response.json();
}
static async post<T>(endpoint: string, data: any): Promise<T> {
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
: 'POST',
method: { 'Content-Type': 'application/json' },
headers: JSON.stringify(data)
body;
})if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return response.json();
} }
17.6 Build Script Example
#!/bin/bash
# scripts/build.sh
echo "🏗️ Building Full Stack Application..."
# Build frontend
echo "📦 Building frontend..."
cd frontend
npm ci
npm run build
# Copy to backend wwwroot
echo "📋 Copying frontend to wwwroot..."
rm -rf ../backend/MyApp.Api/wwwroot/*
cp -r dist/* ../backend/MyApp.Api/wwwroot/
# Build backend
echo "🔧 Building backend..."
cd ../backend
dotnet restore
dotnet build -c Release
echo "✅ Build complete!"
17.7 Key Benefits of This Structure
- Clear Separation: Frontend and backend are clearly separated but in the same repo
- Single Deployment: Deploy as a single .NET application serving both API and SPA
- Development Experience: Hot reload for both frontend (Vite) and backend (dotnet watch)
- Type Safety: Share TypeScript types between frontend and generate from C# models
- Scalability: Can easily split into microservices later if needed
17.8 Additional Tips
- Environment Variables: Use
.env
files for frontend andappsettings.json
for backend - Docker Support: Add
Dockerfile
in root for containerization - CI/CD: Use GitHub Actions to automate builds and deployments
- Testing: Add
frontend/tests/
andbackend/MyApp.Tests/
directories - Shared Types: Consider using tools like NSwag to generate TypeScript clients from your C# API