React FE SPA Architecture
TL;DR
An extensible production front end single page application is complex, even before you add your own code.
If a SPA is the right tool for the job, this is a resonable starting point.
- todo: Write a “Do I need a SPA” decision matrix blog post
A modern React flavored web app, for your consideration
Let’s say, hypothetically, that you get hired by some company. “We’re building a greenfield product refresh. We want to do it in as a single page application using React.” Alright.
Let’s defer the “what is it about this project that requires us to use React” kind of questions and take your new employer seriously. What is required to build an enterprise web app in the Year of our Lord 2026 that is stable, scalable, performant, and can support hundreds of devs throwing code at it from all around the world many hundreds of times a day?
Are your APIs documented with OpenAPI? Congratulations! You’ve got typesafe HTTP clients! Do you need to share cache data between browser contexts? Be careful of staleTime: Infinity and be on your way.
So, the next time you get hired and are told “build the framework,” some permutation of this might be a reasonable place to start.
Container view
---
title: "Frontend SPA — Runtime Component Diagram (C4 Level 3)"
---
flowchart TB
subgraph spa["`**Frontend SPA — Runtime**
_[Container]_`"]
entry["`_«Component»_
**Entry Point**
Bootstraps the app; installs providers and the root error boundary`"]
errorBoundary["`_«Component»_
**Error Boundary**
Catches render errors, shows fallback, reports out`"]
router["`_«Component»_
**Router**
Route table; mounts layouts at outlets, views at routes`"]
guards["`_«Component»_
**Route Guards**
Gate protected routes on session state`"]
subgraph state["`**Global State**
_[system]_`"]
contexts["`_«Component»_
**Contexts**
_[React Context]_
Provider-based dependency injection`"]
store["`_«Component»_
**Global Stores**
_[Zustand]_
Atomic reactive stores`"]
queryCache["`_«Component»_
**Caching Client**
_[TanStack Query]_
Server-state cache & synchronization`"]
authStore["`_«Component»_
**Auth Session**
Tokens & session state`"]
end
subgraph components["`**Components**
_[system]_`"]
layouts["`_«Component»_
**Layouts**
Mounted at router outlets`"]
views["`_«Component»_
**Views**
Rendered at routes`"]
elements["`_«Component»_
**Atoms & Molecules**
Composable UI primitives`"]
end
hooks["`_«Component»_
**Hooks**
Adapters that resolve shared libraries for components`"]
services["`_«Component»_
**Services Interface**
Port / outlet exposing shared libraries to the app`"]
subgraph dataaccess["`**Data Access**
_[system]_`"]
apiClient["`_«Component»_
**API Client**
_[generated, typesafe]_
Typed methods per endpoint`"]
httpClient["`_«Component»_
**HTTP Client**
_[Axios]_
Transport`"]
interceptors["`_«Component»_
**Interceptors**
Auth token, refresh, retry, error normalization`"]
end
subgraph sharedpkgs["`**Shared Packages**
_imported via @, exposed through services_`"]
ds["`_«Component»_
**Design System**
Themed components & tokens`"]
i18n["`_«Component»_
**i18n**
_[i18next]_
Localization + locale bundles`"]
logging["`_«Component»_
**Logging / Telemetry**
Structured logging & error capture`"]
end
end
api["`_«External System»_
**External APIs**
Backend services the app consumes`"]
idp["`_«External System»_
**Identity Provider**
OIDC / OAuth issuer`"]
errorSink["`_«External System»_
**Error / Telemetry Sink**
e.g. Sentry`"]
entry -->|"wraps app in"| errorBoundary
entry -->|"mounts"| router
entry -->|"provides at root"| contexts
router -->|"protects routes with"| guards
router -->|"mounts at outlets"| layouts
router -->|"renders at routes"| views
guards -->|"checks session in"| authStore
views -->|"composed from"| elements
layouts -->|"composed from"| elements
views -->|"read / write"| store
views -->|"read server state"| queryCache
queryCache -->|"fetches via"| apiClient
apiClient -->|"built on"| httpClient
httpClient -->|"applies"| interceptors
httpClient -->|"requests"| api
interceptors -->|"attach token from"| authStore
interceptors -->|"refresh token via"| idp
authStore -->|"authenticates via"| idp
views -->|"consume"| hooks
elements -->|"consume"| hooks
hooks -->|"resolve lib via"| services
services -->|"exposes"| ds
services -->|"exposes"| i18n
services -->|"exposes"| logging
errorBoundary -->|"reports to"| logging
interceptors -->|"report errors to"| logging
logging -->|"ships to"| errorSink
classDef ext fill:#8a8a8a,stroke:#666,color:#fff;
class api,idp,errorSink ext;
---
title: "Frontend SPA — Runtime Component Diagram (C4 Level 3)"
---
flowchart TB
subgraph spa["`**Frontend SPA — Runtime**
_[Container]_`"]
entry["`_«Component»_
**Entry Point**
Bootstraps the app; installs providers and the root error boundary`"]
errorBoundary["`_«Component»_
**Error Boundary**
Catches render errors, shows fallback, reports out`"]
router["`_«Component»_
**Router**
Route table; mounts layouts at outlets, views at routes`"]
guards["`_«Component»_
**Route Guards**
Gate protected routes on session state`"]
subgraph state["`**Global State**
_[system]_`"]
contexts["`_«Component»_
**Contexts**
_[React Context]_
Provider-based dependency injection`"]
store["`_«Component»_
**Global Stores**
_[Zustand]_
Atomic reactive stores`"]
queryCache["`_«Component»_
**Caching Client**
_[TanStack Query]_
Server-state cache & synchronization`"]
authStore["`_«Component»_
**Auth Session**
Tokens & session state`"]
end
subgraph components["`**Components**
_[system]_`"]
layouts["`_«Component»_
**Layouts**
Mounted at router outlets`"]
views["`_«Component»_
**Views**
Rendered at routes`"]
elements["`_«Component»_
**Atoms & Molecules**
Composable UI primitives`"]
end
hooks["`_«Component»_
**Hooks**
Adapters that resolve shared libraries for components`"]
services["`_«Component»_
**Services Interface**
Port / outlet exposing shared libraries to the app`"]
subgraph dataaccess["`**Data Access**
_[system]_`"]
apiClient["`_«Component»_
**API Client**
_[generated, typesafe]_
Typed methods per endpoint`"]
httpClient["`_«Component»_
**HTTP Client**
_[Axios]_
Transport`"]
interceptors["`_«Component»_
**Interceptors**
Auth token, refresh, retry, error normalization`"]
end
subgraph sharedpkgs["`**Shared Packages**
_imported via @, exposed through services_`"]
ds["`_«Component»_
**Design System**
Themed components & tokens`"]
i18n["`_«Component»_
**i18n**
_[i18next]_
Localization + locale bundles`"]
logging["`_«Component»_
**Logging / Telemetry**
Structured logging & error capture`"]
end
end
api["`_«External System»_
**External APIs**
Backend services the app consumes`"]
idp["`_«External System»_
**Identity Provider**
OIDC / OAuth issuer`"]
errorSink["`_«External System»_
**Error / Telemetry Sink**
e.g. Sentry`"]
entry -->|"wraps app in"| errorBoundary
entry -->|"mounts"| router
entry -->|"provides at root"| contexts
router -->|"protects routes with"| guards
router -->|"mounts at outlets"| layouts
router -->|"renders at routes"| views
guards -->|"checks session in"| authStore
views -->|"composed from"| elements
layouts -->|"composed from"| elements
views -->|"read / write"| store
views -->|"read server state"| queryCache
queryCache -->|"fetches via"| apiClient
apiClient -->|"built on"| httpClient
httpClient -->|"applies"| interceptors
httpClient -->|"requests"| api
interceptors -->|"attach token from"| authStore
interceptors -->|"refresh token via"| idp
authStore -->|"authenticates via"| idp
views -->|"consume"| hooks
elements -->|"consume"| hooks
hooks -->|"resolve lib via"| services
services -->|"exposes"| ds
services -->|"exposes"| i18n
services -->|"exposes"| logging
errorBoundary -->|"reports to"| logging
interceptors -->|"report errors to"| logging
logging -->|"ships to"| errorSink
classDef ext fill:#8a8a8a,stroke:#666,color:#fff;
class api,idp,errorSink ext; Each of these probably merits its own write up (or set of write-ups). Future work
Completeness
There are additional infrastructural pieces that are not in this diagram that might be a part of your project, such as
- Alternative BE / FE communication methods (RPC, Web Sockets, SSE’s, GraphQL)
- Search / Keyboard Shortcuts / CLI management
- Push notifications
- Auto updating
- Data sharing between browser contexts
- Gestural input controller
- Big hairy print server integrations
- Big hairy PoS integrations
- Local First / PWA
Once you’ve got the structure of your application penned down in broad strokes, it’s fairly straight forward to extend given this as a starting point.
Thanks
Brycen Ainge for being a great listener, a great collaborator, and an all around great colleague. I’ll never write error handling the same again.
Josh Bendson for introducing me to rigorous C4 diagrams and the patience to correct me while I stumbled through the concept.