New38 new lessons added since Sep 10!
Explore What's New โ†’
Software Development Atlas
Engineering JudgmentDecision Guides

Frontend Modernization Decision Guide: Upgrade, Replace, Wrap, or Delete?

Choose how to handle legacy frontend dependencies and modules by business criticality, compatibility, maintenance health, coupling, test confidence, reversibility, and migration cost instead of applying one modernization rule everywhere.

EvolvingVerified Sep 22, 2026Review target: 180 days

Personal learning atlas by Tran Trong Thuc ยท About this Atlas ยท Atlas last updated Sep 22, 2026

Frontend Modernization Decision Guide: Upgrade, Replace, Wrap, or Delete?

TL;DR

Legacy modernization fails when every problem receives the same treatment.

  • "Old" does not automatically mean replace.
  • "Deprecated" does not automatically mean rewrite today.
  • "Beta" does not automatically mean forbidden.
  • "Custom wrapper" does not automatically mean keep.
  • "We could build it ourselves" does not automatically mean we should.

The useful decision is not old vs new. It is: what is the smallest change that reduces the important risk while preserving reversibility?

๐Ÿ’ก Rule of thumb: Prefer the option that removes the current blocker with the smallest irreversible commitment. Escalate from keep โ†’ upgrade โ†’ wrap โ†’ replace โ†’ local rewrite only when evidence justifies the extra migration surface.

  • Keep when the capability is stable and does not block the target architecture.
  • Upgrade when behavior and ownership remain valid but compatibility is stale.
  • Wrap temporarily when you need a seam to move callers before changing implementation.
  • Replace when maintenance, compatibility, or product constraints make the current dependency a recurring blocker.
  • Delete when the capability is unused, duplicated, or no longer part of the product.
  • Fatal pitfall: choosing a rewrite because the code is embarrassing rather than because the current boundary creates measurable risk.

Decision frame

For the dependency, module, or subsystem in question, write down:

  • Which user journeys depend on it?
  • Is it on a critical revenue, auth, publishing, or destructive path?
  • Does it block the target React/runtime/build upgrade?
  • Is the current behavior well understood and protected by tests?
  • Is the upstream package maintained and compatible with your target?
  • Does the application depend on library-specific types or behavior at many call sites?
  • Is there a stable application-level contract you can introduce?
  • Can the change be rolled back independently?
  • Is the capability still needed?

The options

Keep

Keeping is a valid modernization decision when:

  • behavior is stable;
  • maintenance burden is low;
  • target runtime remains supported;
  • security and supply-chain posture are acceptable;
  • replacement does not unlock meaningful product or delivery value.

Keeping should still have an owner. "Nobody touched it for years" is not the same as "we intentionally accept it."

Upgrade

Upgrade when the capability remains appropriate but the version or integration is stale.

Typical examples:

  • React-compatible router version exists;
  • Redux store setup can move to Redux Toolkit without changing product state ownership yet;
  • a library has a supported major version with a documented migration path.

Upgrading is strongest when you can preserve the application contract and change implementation underneath.

Wrap temporarily

A temporary wrapper is useful when you need to separate caller migration from implementation replacement.

The wrapper should expose application semantics, not replicate the old library API. It should also have an explicit deletion condition.

Replace

Replace when the existing package or module creates repeated structural cost:

  • unsupported target runtime;
  • abandoned or incompatible upstream;
  • critical prerelease dependency with no acceptable operating contract;
  • security issue that cannot be patched safely;
  • library-specific API leaked across the application;
  • product requirements now diverge from the tool's model.

Replacement is not automatically a full rewrite. It can be route-local, feature-local, or adapter-local.

Delete

Deletion is often the highest-leverage modernization move.

Delete when:

  • code is unreachable;
  • feature usage is effectively zero and product agrees to retire it;
  • two libraries perform the same job;
  • compatibility scaffolding no longer has callers;
  • a derived state or wrapper exists only because older architecture required it.

Do not spend upgrade budget on dead capability.

Decision matrix

Frontend modernization option matrix
CriterionKeepUpgradeWrap temporarilyReplaceDelete
Primary goalPreserve stable capabilityRestore compatibility/supportCreate a migration seamRemove structural blockerRemove unnecessary capability
Migration surfaceMinimalUsually bounded if contract stays stableModerate now, enables smaller later cutoverPotentially highLow to high depending on hidden consumers
Best evidenceStable behavior + supported targetUpstream migration path + regression evidenceMany callers + separable app semanticsRecurring incompatibility/maintenance/security costUsage/reachability evidence
ReversibilityHighUsually high with controlled version rollbackHigh if adapter is narrowLower as callers/contracts moveLow if behavior was still needed
Common trapIgnoring future blockersBundling refactors into version bumpPermanent wrapper taxBig-bang rewriteDeleting based on assumption rather than evidence

The matrix is not a scorecard. One option can be right for one route and wrong for another package in the same application.

Add business criticality before technical elegance

A dead chart library on an internal dashboard and an unsupported payment form package may both be "old." Their migration priority is not equal.

Useful priority dimensions:

  • business impact if broken;
  • frequency of change;
  • compatibility pressure;
  • security/supply-chain exposure;
  • blast radius;
  • test/observability confidence;
  • reversibility;
  • migration effort.

When local rewrite is justified

A local rewrite can be rational when:

  • the capability is small enough to understand end-to-end;
  • current implementation is tightly coupled to an obsolete model;
  • behavior can be characterized;
  • the new boundary can ship independently;
  • rollback is possible;
  • the rewrite does not require replacing the entire application shell.

"Rewrite the date input feature" and "rewrite the whole frontend" are fundamentally different risk profiles.

Production micro-scenario: replacing the form stack everywhere

A team decides their eight-year-old form library is embarrassing and replaces it across 140 forms in one program. The old library was ugly but stable. The new library changes validation timing, dirty-state semantics, and submit behavior. Weeks of UI regressions follow.

  • Impact: a cosmetic modernization goal expands into a product-wide behavioral migration with difficult rollback.
  • Root cause: the team evaluated package age and API aesthetics instead of migration surface and business criticality.
  • Correct pattern: identify whether the old form stack actually blocks target upgrades; if replacement is justified, introduce a feature-level seam and migrate high-change forms first while preserving behavior evidence.

Check your mental model

Scenario: A deprecated date library is used through one small adapter in 12 routes. It still works on the target runtime, has no active security issue, and the product has higher-risk React and router blockers. Should replacing the date library be phase one?

Show the reasoning

Probably not.

Deprecation is a real maintenance signal, but the adapter already limits migration surface and the library does not block the target runtime. Record the replacement plan and ownership, then prioritize blockers with higher business or compatibility consequence. Modernization sequencing is about risk reduction, not clearing every warning first.

Decision checklist

  • Need: Confirm the capability is still used and valuable.
  • Criticality: Identify user journeys and business consequence if it breaks.
  • Compatibility: Determine whether it blocks the target runtime or framework.
  • Health: Check maintenance, deprecation, prerelease, and security signals.
  • Coupling: Measure how widely library-specific contracts leak into application code.
  • Evidence: Capture behavior with tests/telemetry before a high-surface change.
  • Seam: Prefer a stable application contract when caller migration and implementation replacement can be separated.
  • Reversibility: Define rollback before selecting a high-cost replacement.
  • Scope: Prefer feature-local replacement over whole-app rewrite.
  • Deletion: Remove unused capability instead of upgrading it.

Sources

On this page