React.NETArchitecture

React + .NET: The Architecture Decisions That Matter in Enterprise Projects

8 min read

I've been building React frontends on .NET backends for most of my career — at ioNob, at Tech Mahindra, and for the past few years at Conga. These are the architecture decisions that actually matter when the codebase lives longer than the initial sprint.

The API contract is everything

The boundary between your .NET backend and React frontend is a contract. Violate it carelessly and you get: frontend breakage when backend changes, over-fetching that tanks performance, and N+1 queries because the frontend team didn't know what the backend was doing.

What works: define your API contracts explicitly, early, using OpenAPI/Swagger. Generate TypeScript types from the spec (openapi-typescript is solid). Now both sides are working from the same source of truth, and breaking changes are caught at the type level before they hit production.

State colocation beats centralisation

Redux was the answer in 2018. By 2024, most React apps are better served by colocating state with the components that own it — React Query for server state, local useState/useReducer for UI state, and Zustand for the small amount of genuinely global state (auth, theme, notifications).

Enterprise apps that started with "everything in Redux" are expensive to maintain. The reason is that global state creates invisible coupling — changing anything requires understanding everything. Colocated state is easier to reason about, test, and delete.

When BFF (Backend for Frontend) actually helps

A BFF is an API layer that exists specifically to serve your frontend. It aggregates calls to multiple microservices, shapes responses for the UI, and handles auth token exchange. The pattern is genuinely useful when: you have multiple downstream services, your UI data shapes don't match your domain models, or you need to aggregate data before rendering.

It's not useful when: you have a monolith and a single frontend. Adding a BFF to that setup adds a deployment unit, another thing to maintain, and latency — for no real benefit.

At Conga I've worked with real BFF architectures where the pattern earns its keep. I've also seen teams add BFF layers to simple CRUD apps because it was fashionable. Know the difference.

.NET specifics that matter

Minimal APIs vs controllers: For new .NET 8+ services, Minimal APIs are cleaner for simple endpoints. Controllers still make sense for complex domains with lots of action filters, policy-based auth, and versioning needs.

EF Core N+1: This will destroy your application performance in production. Use explicit includes, projection to DTOs, and keep an eye on the query logs. EF Core's lazy loading is seductive and dangerous.

Async all the way down: Make every I/O operation async. Don't mix sync and async. Don't use .Result or .Wait(). This is basic, but I've seen it done wrong in codebases that have been running for years.

The one pattern I keep coming back to

Feature folders over type folders. Instead of /controllers, /services, /models, organise by domain feature: /conversations, /knowledge, /billing. Each folder contains everything for that domain. It's easier to navigate, easier to delete features, and the blast radius of changes stays contained.

This applies to both the .NET side (NestJS modules follow this naturally) and the React side (colocate components with their pages, not in a global /components folder).