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.