Frameworks are transient by nature.
They emerge, gain traction, dominate discussions for a few years, and are eventually replaced or fundamentally reworked. This is not a flaw of frontend development; it is a natural consequence of rapid innovation in tooling, browsers, and developer expectations.
The mistake many teams make is not choosing the “wrong” framework, but assuming that the framework they choose today will still shape their codebase in the same way tomorrow.
What should remain stable over time is not the framework, but the business logic of an application: the rules, validations, workflows, and constraints that represent real-world requirements.
Unfortunately, in many frontend projects, these two concerns are tightly intertwined.
When Frameworks Become Architecture
In theory, a framework is just a tool.
In practice, it often becomes the architecture.
Business rules are implemented inside components.
Validation logic depends on framework-specific reactivity models. Application workflows are bound to lifecycle hooks and UI state.
At first, this feels efficient. Everything is close together. The mental overhead is low, and progress is fast. But this efficiency is deceptive.
Over time, the framework stops being a replaceable detail and starts defining how the system works at its core. At that point, changing the framework is no longer a UI problem — it is a fundamental rewrite.
The Long-Term Cost of Coupling
Framework coupling rarely hurts in the short term.
It hurts later, when the context has changed.
Teams encounter the problem when they try to:
- migrate to a new framework version
- introduce a second UI (for example, mobile or desktop)
- reuse logic in a different environment
- improve test coverage in a meaningful way
What they discover is that business logic cannot be extracted without also pulling in framework concepts. Tests require component rendering, lifecycle simulation, and extensive mocking. Small changes ripple through the UI layer, even when the underlying rules remain unchanged.
This is not a technical limitation.
It is an architectural decision that was made implicitly, often without being discussed.
Separate What Changes from What Must Endure
A useful architectural principle is to separate concerns based on their expected rate of change.
Frameworks change quickly.
Business rules change slowly.
UI paradigms evolve.
Domain concepts tend to persist.
This leads to a simple but powerful guideline:
The UI layer coordinates. The domain layer decides.
The responsibility of the UI is to handle user interaction and presentation. It gathers input, invokes application logic, and renders outcomes. It should not contain rules about what is valid, what is allowed, or what must happen next.
Those decisions belong to code that does not depend on any UI technology.
Example (Pseudo Code): UI Orchestrates, Domain Decides
The core idea is that the UI layer should not contain rules, workflows, or side effects. It should only coordinate user interaction.
Bad Way: Mixed concerns (UI + domain + side effects)
// UI component (framework code)
component CheckoutForm {
state email = ""
onSubmit() {
if not email.contains("@") {
showError("Invalid email")
return
}
response = http.post("/checkout", { email })
if response.ok {
navigate("/success")
} else {
showError("Checkout failed")
}
}
render() { ... }
}
This looks harmless. But now your validation rules and checkout workflow are locked into:
- component lifecycle
- framework state management
- UI-level error handling
Reusing or testing the logic requires the framework to be present.
Right Way: Separated concerns (domain + application logic independent of UI)
// Domain / application layer (plain code, no framework)
function validateEmail(email): Result {
if email is empty -> return Error("Email is required")
if not email.contains("@") -> return Error("Invalid email")
return Ok()
}
async function submitCheckout(email, httpClient): Result {
validation = validateEmail(email)
if validation is Error -> return validation
response = await httpClient.post("/checkout", { email })
if not response.ok -> return Error("Checkout failed")
return Ok()
}
// UI component (framework code)
component CheckoutForm {
state email = ""
state error = null
state loading = false
onSubmit() {
loading = true
error = null
result = await submitCheckout(email, http)
loading = false
if result is Error {
error = result.message
} else {
navigate("/success")
}
}
render() { ... }
}
The "domain" functions are now:
- callable from anywhere (UI, CLI, background jobs, tests)
- testable without rendering a component
- reusable even if the UI framework changes
The UI stays slim and replaceable.
Why This Is Not Overengineering
This kind of separation is sometimes dismissed as theoretical or excessive, especially in frontend development. That criticism usually comes from projects that have not yet lived long enough to feel the consequences.
The separation does not require complex patterns or heavy abstractions. It requires discipline in deciding where logic lives.
Plain functions that implement validation, workflows, and side effects can exist without any knowledge of the framework. The UI becomes a thin adapter that translates user actions into function calls and renders the result.
This approach scales down as well as up. It is just as useful for small applications as it is for large ones.
Testability Is a Side Effect, Not the Goal
One immediate benefit of this separation is testability, but it is not the primary reason to do it.
When core logic is independent of the UI, it can be tested without rendering components, without simulating user interaction, and without bootstrapping a framework runtime. Tests become faster and more focused. Failures are easier to diagnose.
More importantly, tests begin to reflect business behavior, not UI mechanics. That shift alone improves code quality.
Framework Evolution Is Not Incremental
A common assumption is that framework upgrades are mostly incremental. In reality, frameworks often change their mental models.
Angular illustrates this clearly.
Within a relatively short timeframe, teams moved from Angular 14-era patterns dominated by Observables, RxJS pipelines, and NgRx-driven state management to a landscape where Angular 21 promotes Signals as a core reactivity primitive. This is not a cosmetic change. It affects how state is represented, how dependencies are tracked, and how updates propagate through the system.
Applications that embedded business logic deeply into these framework-specific paradigms faced significant refactoring pressure - even when the actual business requirements remained unchanged.
The lesson is not that Angular is unstable. The lesson is that frameworks evolve faster than business logic.
Architecture as Risk Management
Architecture is often discussed in terms of elegance or purity. In practice, it is about risk management.
A codebase where business logic is framework-agnostic has:
- lower migration risk
- lower refactoring cost
- a longer useful lifespan
The goal is not to predict the future, but to avoid unnecessary constraints. When the framework changes direction - and it will - you want that change to be localized, not systemic.
Frameworks Are Replaceable by Design
Framework authors know their tools will evolve. That is why APIs change, paradigms shift, and new abstractions are introduced. Expecting a framework to remain stable for a decade is unrealistic.
What can remain stable is your understanding of the domain and the rules that govern it.
Treating frameworks as replaceable details is not an academic exercise. It is a pragmatic response to the reality of modern software development.
A Question Worth Taking Seriously
If you were forced to replace your frontend framework in five years:
- Which parts of your system would survive unchanged?
- Which parts would need to be rewritten from scratch?
If the answer is "almost everything needs to be rewritten," then the framework is not just a tool - it has become your architecture.
And that is a choice worth reconsidering.

Top comments (1)
Coupling of frameworks with logic is somewhat by design (wendor locking), developers find comfort in problems paired with how too-s.
It is not uncommon that teams that ship fast and break things, rework later/never win the market. Long term benefits of good design is not measurable by quarters report.
Thank you, good article, I agree on point :D