How to Choose the Right Frontend Architecture for a Large Web Application

Architecture is the set of decisions that are expensive to change later. The structure, component, state and API decisions that matter most in large web applications.

"Frontend architecture" sounds abstract until you have lived without it. Then it becomes very concrete: a one-line change that breaks three unrelated screens, a state bug nobody can reproduce, a new developer who needs a month before they can ship safely, and a build that takes longer every sprint.

Architecture is simply the set of decisions that are expensive to change later. For a large web application, these are the ones that matter most.

Start with the constraints, not the tools

Before picking libraries, write down the forces acting on the application:

  • Size and lifespan. Twenty screens for eighteen months, or two hundred screens for ten years?
  • Team shape. One team, or several teams owning different areas?
  • Users and devices. Internal users on desktops, or customers on mid-range phones over mobile data?
  • Data characteristics. Mostly read-only content, or highly interactive, real-time, collaborative data?
  • SEO and first load. Does a search engine need to see the page? Does first-load speed affect revenue?
  • Integration surface. How many APIs and third-party services does the UI talk to, and how stable are they?

Most architecture mistakes are not bad choices in isolation. They are good choices for a different set of constraints.

Structure the code around features, not file types

A common starting structure groups by technical type: components/, services/, hooks/, utils/. It works for small projects. At scale, every feature is scattered across every folder, and nothing tells you what depends on what.

A feature-oriented structure scales better:

src/
  app/            # bootstrapping, routing, providers
  features/
    orders/       # everything the Orders feature needs
      api/
      components/
      hooks/
      state/
      index.ts    # the feature's public surface
    customers/
  shared/
    ui/           # design-system components, no business logic
    lib/          # framework-agnostic helpers

The important rule is the dependency direction: features may use shared, but shared must never import from a feature, and features should talk to each other only through their public index.ts. A lint rule that enforces this is worth more than a long document explaining it.

Design components in layers

Not every component should be allowed to do everything. A practical layering:

  1. Primitives — buttons, inputs, modals, layout. No knowledge of the domain. This is your design system.
  2. Domain components — OrderStatusBadge, CustomerCard. Know the business vocabulary, but receive data through props.
  3. Containers / feature screens — fetch data, handle loading and error states, coordinate actions.

When a primitive starts importing API code, or a domain component starts fetching its own data, the layers blur and reuse becomes painful. Keep data fetching near the top and pass data down.

A shared component library pays for itself

Once more than one team builds UI, a shared component library — even a small one with buttons, form fields, tables and dialogs — prevents a slow drift into five slightly different date pickers. Pair it with design tokens (colours, spacing, typography as variables) so visual changes happen in one place.

Decide where state lives — explicitly

Most frontend complexity is state complexity. It helps to separate state into kinds, because each has a different best home:

Kind of stateExamplesUsual home
Server stateOrders, products, user profileA data-fetching/cache layer (e.g. TanStack Query, RTK Query, Angular services with signals)
URL stateFilters, pagination, selected tabThe URL — so it is shareable and survives refresh
Form stateField values, validationA form library, local to the form
Local UI stateIs this dropdown open?The component itself
Global client stateTheme, feature flags, auth sessionA small global store or context

The most common anti-pattern I see is putting server data into a global client store and then hand-writing caching, refetching and invalidation logic. A dedicated server-state layer removes a large category of bugs.

Treat the API boundary as part of the architecture

The frontend should not know every quirk of every backend response. Put a thin API layer between the two:

  • Typed request and response models (generated from OpenAPI or GraphQL schemas where possible).
  • One place for authentication headers, retries and error normalisation.
  • Mapping from backend shapes to the shapes your UI actually needs.

When a backend field is renamed, you change one mapper instead of forty components. If you control the backend too, a backend-for-frontend (BFF) endpoint that returns exactly what a screen needs can remove a lot of client-side orchestration.

Plan for performance from the start

You do not need to optimise everything on day one, but some decisions are hard to retrofit:

  • Route-level code splitting so users download only the screens they visit.
  • A rendering strategy per area: static or server-rendered for public pages, client-rendered for authenticated dashboards is a common and sensible split.
  • Performance budgets in CI — for example, fail the build if the main bundle grows beyond an agreed size.
  • Virtualised lists for any table that can reach thousands of rows.

Should you use micro-frontends?

Micro-frontends solve an organisational problem: multiple teams that need to deploy independently. They add real costs — duplicated dependencies, cross-app communication, version alignment and more complex tooling.

If you have one or two teams, a well-structured modular monolith with clear feature boundaries will almost always serve you better. Consider micro-frontends when team autonomy and independent release cycles are a genuine, current bottleneck, not a future possibility.

Write the decisions down

Architecture that lives only in one senior developer's head is a risk. Lightweight Architecture Decision Records (ADRs) — a short file per decision describing the context, the choice and the consequences — make the reasoning visible. When someone asks "why do we do it this way?" in two years, the answer exists.

A checklist for your next large application

  • Constraints documented: size, lifespan, teams, users, SEO, integrations.
  • Feature-based structure with enforced dependency rules.
  • Component layers: primitives, domain components, containers.
  • Explicit homes for server, URL, form, local and global state.
  • A typed API layer between UI and backend.
  • Code splitting and a performance budget in CI.
  • Micro-frontends only when team autonomy truly requires them.
  • ADRs for decisions that are expensive to reverse.

Good frontend architecture is rarely clever. It is a set of boring, consistent decisions that let many people change a large codebase safely. If you are about to start a large application — or you are already feeling the pain of one that grew without a plan — an architecture review early on is one of the highest-leverage investments you can make.

  • Frontend Architecture
  • Scalability
  • State Management
  • API Integration

Need help with your project? Consult Ritesh Kumar.

Have a similar technical challenge?

Let's discuss your application, architecture or development requirements.

All articles

Consulting 5 min read

When Should a Business Hire a Technical Consultant?

New product with no senior lead, an app that keeps getting harder to change, a bug nobody can fix — seven situations where outside technical expertise pays for itself, and a few where it does not.