Architecture
Intermediate 18 min read Architecture

Frontend Architecture

Frontend architecture is the discipline of structuring UI code so it stays fast and maintainable as teams and features grow: design systems for shared visual language, component architecture for composability, and module federation plus monorepos for shipping independent frontends. Done well, the frontend scales like a product instead of a pile of components.

design systemcomponent architecturemodule federationmicro-frontendsmonorepodesign tokensstorybookcomposition
Introduction

What is this?

Frontend architecture covers how UI code is organized and delivered: a design system provides the shared visual language (tokens, components, docs); component architecture defines how components compose, own state and communicate; and module federation or a monorepo decides how teams ship and version their slices of the interface. It is the frontend answer to the same scalability questions the backend answers with services and boundaries.

Motivation

Why do you need it?

  • A design system centralizes visual decisions so twenty screens cannot drift into twenty shades of blue.
  • Well-defined component boundaries make UI behavior testable and reusable instead of copy-pasted.
  • Monorepos make shared code, atomic changes and cached CI for many frontends tractable.
  • Module federation lets independent teams deploy UI slices on their own cadence without a coordinated release.
  • Good frontend architecture keeps bundle size, render performance and developer velocity predictable as the app grows.
Think about it

Mental model

Imagine

A city with no building code

Think of

A city with a shared architectural standard

Every building still belongs to a different owner, but shared standards — streets, zoning, facade rules — keep the city coherent. A design system is the frontend's zoning code: individual apps remain independent while the shared standards keep them consistent and interoperable.

Mechanics

How it works

  • A design system publishes tokens (color, spacing, typography) and presentational components with a versioned API, so product teams consume rather than reinvent the visual language.
  • Component architecture separates containers (they know data and state) from presentational components (they know markup and styles), keeping components composable and predictable.
  • State stays close to where it is used and is lifted or shared only when siblings truly need it; providers and hooks make cross-cutting concerns explicit.
  • A monorepo hosts multiple frontends plus the design system, so shared code is versioned atomically with its consumers and CI builds only what changed.
  • Module federation lets a host app load remotes — separately built and deployed frontends — that expose components over the network, each released on its own cadence.

From a shared design system to independently deployed apps that compose at runtime.

Design tokens
Design system
App teams
Monorepo
Module federation
Independent deploys
Reference

Key commands

npx create-mf-app --name remote --type remote --framework react

Scaffold a module federation remote application.

pnpm --filter @acme/design-system build

Build just the design-system package inside the monorepo.

@acme/design-system:build: done in 1.2s
npx storybook dev --port 6006

Run the component playground to develop components in isolation.

npx webpack serve --config remote/webpack.config.js

Serve the remote app that exposes components at runtime.

npx nx affected -t test

Test only the projects affected by changed files in the monorepo.

node scripts/sync-tokens.mjs

Generate token files from the design export so code and design stay in sync.

Hands-on

Examples

A presentational Button styled from design-system tokens

A presentational Button styled from design-system tokens
// design-system/src/Button.tsx
import { tokens } from "./tokens"
export type ButtonVariant = "primary" | "secondary" | "ghost"
interface ButtonProps {
variant?: ButtonVariant
disabled?: boolean
children: React.ReactNode
}
const styles: Record<ButtonVariant, string> = {
primary: tokens.bg.accent + " " + tokens.text.inverse,
secondary: tokens.border.default + " " + tokens.text.default,
ghost: tokens.text.muted,
}
export function Button({ variant = "primary", disabled = false, children }: ButtonProps) {
return (
<button className={styles[variant]} disabled={disabled} data-variant={variant}>
{children}
</button>
)
}

Button is presentational: it takes props, renders markup and knows nothing about data or state. Its look comes entirely from design-system tokens, so a rebrand touches one file. Consumers compose it with their own data flow instead of editing global styles per screen.

Real world

In production

  • A large retailer splits its storefront into host plus catalog and checkout remotes, each owned by a different team and deployed weekly.
  • A versioned design-system package is consumed by six product teams; a token change propagates to all of them in one release.
  • A monorepo with CI caching rebuilds and tests only affected frontends, cutting a full rebuild from minutes to seconds.
  • Storybook acts as living documentation: engineers verify every visual state of a component before it ships.
Guidelines

Best practices

  • Keep presentational components in the design system and containers (data + state) in the app that owns the feature.
  • Version tokens and components semantically and document breaking changes before deprecating anything.
  • Use a single state source of truth per feature and derive UI state from it instead of mirroring state in props.
  • Treat bundle size as a budget: lazy-load routes and remotes, and keep shared dependencies version-aligned in the monorepo.
  • Make components accessible by default: keyboard, focus and screen-reader behavior are part of the API.
Watch out

Common mistakes

  • Building a 'design system' that is really a component dump with no tokens, docs or versioning discipline.
  • Putting data fetching and business logic inside presentational components, making them impossible to reuse.
  • Copying components between apps instead of publishing them, so the same widget drifts in five places.
  • Composing module federation remotes that share large duplicated dependency bundles, bloating the host download.
Root cause analysis

Common issues

Error
Invalid hook call — react is duplicated
Root cause

Host and remote each load their own copy of React instead of sharing it via federation.

Fix

Add react and react-dom to shared in the module federation config so both sides use the same instance.

Error
Remote module failed to load (TypeError: Failed to fetch)
Root cause

The host could not reach the remote's remoteEntry.js, often a version mismatch or the remote not running.

Fix

Serve the remote app, verify remoteEntry.js returns 200, and align the federation config versions.

Error
Design styles missing in the host
Root cause

The remote's CSS was not extracted, or tokens are scoped to a namespace the host does not load.

Fix

Extract remote CSS in the build and ensure token files are imported before component modules run.

When things break

Debugging tips

  • Reproduce the bug in Storybook first: if it fails there, the issue is the component; if it passes, it is the container or data.
  • Check the shared-dependency map when a federation remote misbehaves — duplicated React instances break hooks and context.
  • Inspect token drift by searching for hard-coded hex colors outside the design-system package.
  • Profile with the browser's coverage and network panels to find which route or remote adds the most bytes.
Interviews

Interview checkpoint

Practice

Hands-on lab

Compose a Design System Across Two Apps
45 min · intermediate
In progress
Objectives
  • Create a monorepo workspace with a shared design-system package
  • Build a presentational Button component from design tokens
  • Consume the design system from two independent apps
  • Verify the shared package resolves identically from both apps

Compose a Design System Across Two Apps

45 min · intermediate

0/5 steps
0%
sandbox · Linux sandbox
$
Try these commands
Test yourself

Quick quiz

0/3
1

What is the main difference between a design system and a component library?

2

In module federation, the host and the remote must ship as a single combined deployable.

3

Three apps hard-code the same shade of indigo, and the brand changes the color. What is the correct fix?

Quick reference

Cheat sheet

Design system
TokensSingle source for color, spacing, type
Presentational componentsNo data, no state — pure props
StorybookLiving docs for every component state
Semantic versioningBreaking changes documented
Multi-app strategy
MonorepoShared code, atomic changes, cached CI
Module federationRuntime composition of remotes
Lazy routesCode-split by route or feature
Shared singletonsOne React instance across remotes
Go deeper

Official documentation