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 IDEAPut 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.
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:
Product change
Rules, flows, permissions, experiments, and what the user is allowed to do.
Presentation change
Visual language, interaction patterns, accessibility, and responsive behavior.
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 clientThis 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.
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.
- 01
The locality test
Can a normal product change be completed mostly inside one feature, or does it require a tour of the repository?
- 02
The interface test
Can you describe what crosses the boundary in product language? “Appointment availability” is a contract. “miscData” is a leak.
- 03
The replacement test
If the API, state library, or UI treatment changed, how much unrelated code would notice?
- 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.
What must be addressable, shareable, and independently rendered?
What can be decided before JavaScript reaches the browser?
Which product capability owns this behavior and vocabulary?
What visual or interaction contract is stable enough to reuse?
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
Choose one change hotspot. Use churn, defects, and coordination pain—not aesthetic dislike—as evidence.
- 2
Name the capability. Give it product vocabulary and an explicit owner.
- 3
Define the narrow interface. Capture current behavior with a small number of boundary tests.
- 4
Route new work through it. Move old paths only when touched; stop creating fresh dependencies on the legacy shape.
- 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.