Skip to content
ks.
Frontend architectureEssay 001

The real unit of frontend architecture is change.

A practical method for designing boundaries that survive shifting requirements, growing teams, and the redesign after next.

Written byKhaled Sarhan
Published
Reading time14 min read

Most frontend architecture conversations begin with nouns: components, stores, hooks, routes, packages. The useful ones begin with a verb: change.

A codebase is not valuable because its boxes are arranged neatly. It is valuable because a team can change the product safely, quickly, and with a reasonable understanding of what will break. The architecture is the set of decisions that makes that possible.

That framing changes the central question. Instead of asking, “Where should this component live?” ask, “What is likely to change with it?” The answer is a far better guide to ownership, dependencies, APIs, tests, and even rendering strategy.

THE CORE IDEA

Put things that change together close together. Put a clear interface between things that change for different reasons.

01 / THE PREMISE

Architecture is a change strategy.

Imagine two codebases with identical folders. Both use feature modules, TypeScript, a component library, and server rendering. One team ships confidently. The other turns every pricing change into a week of regression fixes. Their diagrams look the same; their architectures are not.

The difference is usually hidden in dependency direction and decision ownership. Does the checkout feature own the rules that make it checkout? Or are those rules scattered across generic form helpers, global stores, page components, and “shared” utilities? A folder tree cannot answer that. A change can.

architecture quality=locality × confidence÷coordination cost

This is not a literal formula. It is a forcing function. Good architecture keeps a change local, makes its effect observable, and reduces how many people or modules must coordinate to ship it.

02 / BEFORE THE FOLDERS

Map the vectors of change.

Before reorganizing code, list the forces acting on the product. In most frontend systems they fall into three broad vectors:

01

Product change

Rules, flows, permissions, experiments, and what the user is allowed to do.

02

Presentation change

Visual language, interaction patterns, accessibility, and responsive behavior.

03

Platform change

APIs, authentication, analytics, rendering, caching, and runtime constraints.

Now add cadence. Pricing rules may change every sprint. The typography system may change once a year. Authentication may be stable until the identity provider changes, then affect nearly everything. Frequency alone is not the risk; frequency multiplied by blast radius is.

High-frequency, high-radius areas deserve deliberate interfaces. Low-frequency, local details do not need an abstraction ceremony. This protects the team from both under-design and over-engineering.

03 / THE BOUNDARY RULE

Draw boundaries by cadence, not by file type.

Technical layers—components, hooks, services, utilities—group things that look alike. Product slices group things that change together. For evolving applications, the second is usually the stronger default.

features/
  appointments/
    api.ts          // transport for this capability
    model.ts        // domain types and rules
    queries.ts      // cache policy and orchestration
    ui/             // feature-specific interface
    tests/          // behavior at the boundary

shared/
  ui/               // truly product-agnostic primitives
  platform/         // auth, telemetry, HTTP client

This structure is useful only if the dependency direction is real. A shared button may know about focus states and visual variants; it should not know what “confirm appointment” means. The appointment feature may compose that button and own the language, validation, mutation, analytics event, and success state. Meaning flows inward to the feature, not outward to the design system.

The shared-layer tax

Every item placed in shared acquires an implied compatibility promise. If only one feature uses it, keep it local. Promote it after a second real use reveals the stable shape—not when a first use merely suggests one.

04 / PRESSURE TEST

Four tests for a useful boundary.

  1. 01

    The locality test

    Can a normal product change be completed mostly inside one feature, or does it require a tour of the repository?

  2. 02

    The interface test

    Can you describe what crosses the boundary in product language? “Appointment availability” is a contract. “miscData” is a leak.

  3. 03

    The replacement test

    If the API, state library, or UI treatment changed, how much unrelated code would notice?

  4. 04

    The deletion test

    Could the feature be removed cleanly? Deletability is an underrated measure of coupling.

No system passes every test perfectly. The goal is not purity; it is to make coupling visible enough that the team chooses it consciously.

05 / IN REACT + NEXT.JS

A practical blueprint.

In a modern React application, I apply the change-first model at five boundaries. Each boundary answers a different question.

ROUTE

What must be addressable, shareable, and independently rendered?

SERVER

What can be decided before JavaScript reaches the browser?

FEATURE

Which product capability owns this behavior and vocabulary?

COMPONENT

What visual or interaction contract is stable enough to reuse?

DATA

Who owns freshness, normalization, errors, and the mutation lifecycle?

Start on the server when the work is data access, authorization, or non-interactive composition. Cross into a client component at the smallest useful interaction boundary. This is not about chasing a zero-JavaScript score. It is about keeping server and browser concerns from changing each other accidentally.

Keep remote state close to the feature that interprets it. A query cache is not your domain model, and a global store is not a substitute for ownership. Derive view state where it is consumed; persist only the state that must outlive that view.

Reuse is the outcome of a stable idea, not the starting point of a new one.

06 / EVOLUTION

Change the architecture without a rewrite.

Architecture should improve through product work. A rewrite asks the business to pause while engineers recreate yesterday’s value. A boundary migration lets today’s requirement pay for tomorrow’s structure.

  1. 1

    Choose one change hotspot. Use churn, defects, and coordination pain—not aesthetic dislike—as evidence.

  2. 2

    Name the capability. Give it product vocabulary and an explicit owner.

  3. 3

    Define the narrow interface. Capture current behavior with a small number of boundary tests.

  4. 4

    Route new work through it. Move old paths only when touched; stop creating fresh dependencies on the legacy shape.

  5. 5

    Delete the bridge. Temporary adapters need an exit condition, or they become permanent architecture.

After two or three changes, inspect the result. Did fewer files change? Did review require less context? Did defects remain local? Architecture becomes measurable when you evaluate the path of real work instead of the elegance of a diagram.

07 / TAKE THIS WITH YOU

A short decision record.

Before adding a new abstraction, write five lines. This is enough rigor to expose a weak premise without turning the decision into paperwork.

CHANGE What recurring product change are we making easier?

OWNER Which capability owns the decision?

BOUNDARY What may cross, and in what language?

TRADE-OFF What becomes harder because of this choice?

SIGNAL What evidence would tell us to revisit it?

The best frontend architecture is rarely the cleverest. It is the one that lets the next person locate a decision, understand its edges, and change it without needing a map of the entire system.

Design for the change you can see.
Leave room for the change you cannot.