Modernization proposals often become difficult to defend long before anyone writes new code. Engineering may know the current front end is slowing the team down, but the business hears a different proposition: spend months rebuilding something that already works, accept slower feature delivery in the meantime, and trust an estimate built on a codebase nobody fully understands.
Debating React versus Vue does little to resolve that problem.
The more consequential decisions come earlier. What can be changed without destabilizing the application? Which behavior needs to be protected before anyone touches it? Can old and new code coexist? And can the migration be structured so that the company still gets value if priorities change halfway through?
That is a more useful way to think about legacy JavaScript modernization: not as a framework replacement, but as a sequence of risk-reduction decisions.
What Actually Makes JavaScript "Legacy"?
Old JavaScript is not necessarily legacy JavaScript.
An ES5 application with clear module boundaries, dependable tests, and a build process the team understands may be dated without being particularly dangerous. A two-year-old React application with tangled state, virtually no tests, hundreds of transitive dependencies, and engineers who hesitate before touching core modules may already qualify as legacy.
Age is a weak proxy. Changeability is the better test.
If engineers cannot answer "what else could break if I change this?" with reasonable confidence, the problem is structural regardless of the framework named in package.json.
Several signals tend to appear together:
Signal
What it usually means
Business-critical paths have little or no automated coverage
Releases depend heavily on manual regression testing
The DOM acts as application state
Multiple scripts can change the same nodes without a clear owner
A major framework or dependency is genuinely unsupported
Security and compatibility risk increase over time
Global variables and script load order are the integration mechanism
Modules have weak or nonexistent boundaries
The build configuration is effectively untouchable
A former engineer's setup has become operational infrastructure nobody wants to modify
Dependency upgrades regularly cause unrelated failures
The dependency tree is effectively frozen
New engineers need months to become productive
Knowledge is concentrated in people rather than encoded in tests and boundaries
One symptom on its own may be ordinary maintenance debt. Once several are present, modernization is worth costing seriously.
The subjective assessment should still be paired with harder measurements. Test coverage on the application's three most commercially important flows and the median cycle time for a small feature over the previous two quarters are good starting points. They reveal whether the problem is mostly aesthetic dissatisfaction with an older stack or a measurable inability to change it safely.
jQuery is a useful example of why version matters more than reputation
jQuery is often used as shorthand for "legacy," which is too simplistic.
jQuery 4.0.0 shipped in January 2026, the first major release in almost a decade. The 4.x branch is supported, while 3.x receives critical security fixes. Versions 1.x and 2.x are a different matter: their final releases arrived in May 2016, and those branches have gone roughly a decade without patches.
The installed base is still enormous. According to W3Techs, jQuery was used by 66.5% of all websites in August 2026. Among sites using it, 17.4% were still on version 1 and 4.2% on version 2.
So "we use jQuery" is not much of a modernization argument. "We depend on jQuery 1.9 and an abandoned plugin prevents us from upgrading" is.
Those are different risk profiles. One is largely a technology preference. The other combines an unpatched dependency with a growing future migration cost.
AngularJS illustrates the opposite case. Google ended long-term support on December 31, 2021. The project repository is archived and read-only, with no further official security updates. Third-party extended support can buy time, but it does not remove the underlying migration requirement.
A production AngularJS 1.x application in 2026 therefore has a materially different business case from an application running a supported but unfashionable library.
How Legacy JavaScript Turns Into a Business Problem
Code quality is rarely enough to secure a modernization budget. The stronger case is what the codebase is doing to delivery, hiring, security, and future change.
Feature velocity is usually the first cost to become visible. In a tightly coupled application, changing one area can affect several others in ways that are hard to predict. Engineers compensate rationally: more manual regression, longer release cycles, larger estimates, more defensive implementation work.
They are not necessarily coding more slowly. The path from code change to safe production deployment has become longer.
There is also a people cost. Stack Overflow's 2024 Developer Survey, covering more than 65,000 developers, identified technical debt as the largest source of frustration at 62%, roughly twice the next-highest complaint.
That number is more useful as a hiring and retention signal than as a direct productivity metric. Engineers do evaluate the condition of a stack when considering a role. Meanwhile, the pool of people willing and able to maintain an unsupported framework gets smaller. The awkward result is that companies can end up paying senior-level compensation for maintenance work because the mid-level hiring pool has disappeared.
Security debt accumulates more quietly. A frozen dependency tree does not mean its risk is frozen; newly disclosed vulnerabilities continue to appear against the software already in production. For companies selling into enterprises or operating in regulated markets, the impact may surface in a security review, procurement questionnaire, audit, or urgent remediation request rather than an obvious breach.
Waiting also increases the migration surface. New features continue to be built on patterns that will eventually need to disappear, while people who understand the original architecture gradually leave.
That is one of the better arguments for beginning with a narrow scope. Waiting until the organization can afford a giant modernization project can make the giant project even larger.
A business case built around "innovation," "modern technology," or "keeping up with the market" will understandably struggle. Cycle time, production incidents, onboarding time, unresolved CVEs, recruiting data, and regression effort are much harder to dismiss because they can be measured.
Rewrite, Refactor, or Migrate Incrementally?
For most business applications, incremental migration is the lower-risk default.
A full rewrite can make sense, but the circumstances are narrower than teams often assume. Small codebase size matters. So does an actually unsupported platform, not merely an unpopular one. The business must be willing to absorb slower feature delivery, and the team should have experience running this kind of migration.
The trade-offs become clearer when the decision is broken into inputs rather than treated as a philosophical argument.
Input
Targeted refactor tends to fit
Incremental migration tends to fit
Full rewrite becomes plausible
Problems are small and contained
Medium or large business application
Genuinely small application, perhaps only a few thousand lines
EOL or unsupported framework
Unsupported architecture with no workable coexistence path
Critical areas already have useful coverage
Coverage is weak and must be built around migration targets
Near-zero coverage, but required behavior is documented elsewhere
Tolerance for slower feature delivery
Little disruption required
Features can continue alongside migration
A real, funded, time-boxed delivery slowdown is acceptable
Experience with the destination stack
Some expertise can be developed module by module
Strong expertise and previous migration experience already exist
Joel Spolsky's 2000 essay about Netscape's browser rewrite became the canonical warning against throwing away working software. Netscape lost roughly three years of market position during the process, and the story has shaped engineering thinking ever since.
The lesson should not become "rewrites are always wrong." If a 12,000-line internal application depends on an archived framework, requirements are documented, and coexistence would be harder than replacement, rebuilding it may be the sensible option.
Criteria are more useful than ideology.
The constraint engineering teams often underestimate
Framework age is rarely the deciding factor. Business tolerance for a delivery slowdown often is.
A rewrite does not make the old system disappear during development. The team still has to maintain production, resolve defects, handle security work, and meet contractual obligations while the replacement is being built. In effect, the organization carries two systems' worth of responsibility with roughly one system's engineering capacity.
There is another problem: the new implementation may go a long time without exposure to real user behavior.
If leadership will not tolerate slower feature delivery for the entire rewrite — and that period frequently exceeds the original estimate — the supposed rewrite is not really viable. It tends to turn into an incremental project anyway, except without having been designed for coexistence.
That is a poor place to discover the constraint.
The Strangler Fig Pattern Works on Front Ends Too
Martin Fowler introduced the strangler fig metaphor in 2004 after observing strangler figs in Queensland. The plant grows around a host tree until it can survive independently; Fowler saw a parallel with gradually replacing legacy systems. He later revisited the idea in the context of mobile applications.
The pattern is often associated with backend systems, but the same principle works in browser applications. Instead of routing backend requests through a proxy, the front end needs a boundary that determines which parts remain legacy and which parts belong to the replacement.
Route-level splitting is usually the simplest option. Some URLs continue to render the legacy application while others enter the new one. The boundary is coarse, but understandable, observable, and easy to roll back.
Mount-point embedding provides finer control. A new framework renders inside a DOM node still owned by the old application. Preply described essentially this approach during its Backbone-to-React migration: React components were exposed so that existing Backbone code could create and render them inside selected views.
Web components offer a more neutral boundary. New functionality can be wrapped as custom elements, reducing direct coupling between the old framework and the new one. The setup is heavier, but the contract can survive another framework change later.
Module Federation can also be appropriate, particularly where multiple teams deploy separate front ends independently. For one team modernizing one application, it can introduce more infrastructure than the migration requires. Route-level splitting is usually a more sensible place to start.
There are still cases where rewriting is cleaner
A small internal tool on an EOL framework is the obvious example. If its domain rules are straightforward, requirements exist outside the implementation, the user population is limited, and cutover can be controlled, the coordination cost of incremental coexistence may exceed the value it provides.
A rewrite may also be justified when the legacy architecture makes coexistence impractical — for instance, when a framework assumes exclusive ownership of the rendering lifecycle and cannot be partitioned cleanly.
The warning matters most for customer-facing applications where production behavior is effectively the specification.
Old edge cases are not automatically defects simply because nobody remembers why they exist. Someone may depend on them. Rewriting removes the implementation that encoded those assumptions, so the team needs another way to discover them before users do.
Build the Safety Net Before Changing the Structure
A codebase with no tests does not need to become beautifully tested before modernization can begin. It needs enough protection around the behavior being changed.
Characterization tests are particularly useful here. Instead of asking what a function should do, they record what it does today. Their purpose is not to certify correctness; it is to reveal when migration work changes existing behavior.
That distinction feels uncomfortable when the existing behavior appears wrong.
Suppose a legacy pricing function returns -0.00 for a particular type of refund. Fixing it while adding the test is tempting. During a migration, a safer first move is often to capture the current output, mark the behavior as suspicious, and investigate it separately. Another component may format, compare, or serialize that exact value.
Migration and behavioral correction are different changes. Combining them makes regressions harder to diagnose.
Where a single function mixes DOM manipulation, network calls, and business logic, unit testing may be disproportionately expensive. Golden-master tests can be a practical bridge: run representative inputs through the existing system, record the resulting output or serialized state, and compare future runs against it. The diffs can be ugly. The coverage can still be extremely useful.
Testing priority should follow business risk rather than directory structure:
Priority
What to protect
Why
Checkout, payments, authentication, subscription changes
Failures affect revenue or access
Silent corruption can be more damaging than a visible outage
The module scheduled for migration next
Migration should not start without a local tripwire
End-to-end coverage is relatively cheap and catches broad regressions
Cover opportunistically where the expected value justifies it
The target is not an arbitrary global coverage percentage.
Requiring 80% coverage before modernization begins can easily consume months testing modules that are about to be deleted. A more useful question is local: if an engineer intentionally breaks the module scheduled for migration, does CI fail within one run?
If yes, there is at least a meaningful safety net around that change.
Testing tools in 2026
Jest remains widely used, while Vitest's adoption has been rising quickly enough to challenge it, particularly as Jest satisfaction has declined.
For end-to-end testing, State of JS 2025 data reported through InfoQ put Playwright satisfaction at roughly 91%, compared with 72% for Cypress.
For a legacy application with no meaningful test infrastructure, Playwright can be a pragmatic first layer because it exercises the running product. The team does not have to untangle every module sufficiently to import and unit-test it before gaining regression protection.
Once boundaries improve, lower-level tests become easier to add.
And if Jest is already installed, understood, and doing its job, swapping test runners during a modernization effort is usually unnecessary scope.
A Framework-Agnostic Migration Sequence
Specific migration guides age quickly because framework APIs change. The useful part survives: establish a boundary, prove coexistence, migrate something small, validate it in production, and remove what it replaced.
1. Find a seam before choosing the first feature
A seam is a place where behavior can change without requiring changes throughout the surrounding application.
Routes are common seams. So are self-contained UI regions with a clear data contract and modules that communicate through events instead of directly sharing mutable state.
This changes the usual prioritization logic. The most commercially important feature is not necessarily the best first target. If it sits in the most tightly coupled part of the product, it is a poor training ground for an unfamiliar migration pattern.
A moderately important feature with a clean boundary may teach the team far more at much lower risk.
2. Prove that old and new code can coexist
Before migrating a real feature, mount the smallest possible component from the destination stack inside the existing application and put it into production behind a flag.
A static label is enough.
The point is not the component. It is everything around it.
Hybrid execution exposes bundler conflicts, duplicated libraries, CSS leakage, global event assumptions, and differences between imperative and declarative rendering. Discovering those issues against a placeholder in week one is far cheaper than discovering them four months later while replacing checkout.
3. Pick a visible but containable first migration
A dashboard widget, settings panel, search filter, or one stage of a multi-step form can work well. Users interact with it often enough to generate useful feedback, while failure remains relatively easy to isolate.
Starting with the hardest module because "if we can migrate that, we can migrate anything" sounds rigorous but usually has the opposite effect. The team takes its highest-uncertainty problem at the exact moment it has the least experience with coexistence, observability, rollout, and rollback.
Two useful accounts from teams that have done the work are Preply's Backbone-to-React migration and Jack Franklin's Songkick migration from Angular to React. The latter is especially useful because it discusses problems and regrets rather than presenting migration as a frictionless success story.
4. Put each increment in front of real traffic
Ship the migrated module before beginning several more.
Expose it to a controlled share of users. Compare errors, performance, and relevant product metrics with the legacy implementation. If the new path behaves differently, there is still a small enough surface to understand why.
Batching four or five modules before exposing any of them removes one of incremental migration's biggest advantages: the ability to use each step as evidence for the next.
5. Treat deletion as part of the migration
A feature is not migrated merely because the new version exists.
The old implementation should be removed, along with dependencies that no longer serve anything else.
Keeping the old code indefinitely creates two sources of truth and two sets of bugs. In some architectures it also means sending both frameworks to the browser. That is how a temporary hybrid architecture quietly turns into the permanent architecture.
For planning purposes, migration tickets should therefore include retirement work rather than treating deletion as cleanup for "later."
The coexistence mechanics vary by starting point:
Migration
Practical coexistence model
Main source of friction
Mount new components into legacy-owned DOM regions
Legacy scripts may mutate DOM the new framework assumes it owns
Route split or documented hybrid upgrade path
Dependency injection and two change-detection models
Render React components from Backbone views
Event-driven model state and declarative props coexist temporarily
Server-rendered + jQuery → SPA
Split at the server or route level
Authentication and session continuity across rendering models
Modernize the Build Pipeline Before the Framework When You Can
Some "framework modernization" projects actually contain three separate problems: an aging framework, a fragile build pipeline, and an unpatched dependency tree.
They do not always need to be solved at the same time.
Build tooling is often the least glamorous part of the project and one of the better places to begin. It is cheaper and less invasive than changing application behavior, while making later work easier.
A useful Phase 0 covers four things: inventorying dependencies and their support status; introducing a module system where the application still depends on script load order and globals; establishing explicit browser targets so modern JavaScript can be transpiled safely; and getting CI to run at least a meaningful smoke test and linting.
Moving from globally coupled scripts to modules is particularly important. It begins creating the boundaries that testing and incremental replacement depend on.
Vite versus Webpack is not the point
The 2025 State of JS data shows an unusual split between usage and satisfaction. Webpack still had slightly higher usage — 87% versus Vite's 84% — while Vite satisfaction reached 98% compared with 26% for Webpack, down from 36% the previous year, according to InfoQ. State of JS also reported Vite downloads moving past Webpack during 2025, with Rolldown positioned to support a faster Vite toolchain going forward.
That does not automatically make a Webpack-to-Vite migration good Phase 0 work.
Survey satisfaction is influenced heavily by greenfield projects. Recreating years of custom loaders, plugins, environment assumptions, and build behavior is a very different job from choosing Vite for a new application.
If the current Webpack build is reliable and engineers understand it, replacing it may be discretionary work. If builds are painfully slow, critical plugins are abandoned, or nobody can safely alter the configuration, build modernization has a much clearer payoff.
For teams that need a visible technical win within a quarter before asking for a larger modernization budget, Phase 0 can be enough. If an EOL framework creates an immediate security deadline, however, keep the build work narrow and move quickly toward the actual source of risk.
Who Should Own the Migration?
Technical strategy and staffing strategy are connected. The right structure depends less on a generic preference for internal or external teams than on the actual bottleneck: domain knowledge, specialist expertise, or available capacity.
In-house only
Senior staff augmentation
Dedicated external team
Internal team already has migration experience and spare capacity
Domain knowledge exists internally, but specific migration or target-stack experience is missing
Migration must proceed while the internal team continues feature delivery
High, because engineers work inside the existing team
Lower unless transfer is designed into the engagement
Weeks to months depending on domain complexity
Lower than it appears because delayed feature work is an opportunity cost
Higher when scope and capacity are contracted
Impact on normal product delivery
Highest; internal leads still direct the work
Moderate when the external team owns delivery outcomes
Feature work wins every sprint
Specialists are underused because ownership is unclear
Knowledge leaves at the end of the engagement
Keeping everything in-house is often the right answer when the team has done this before. The risk appears when "in-house" really means "the same engineers will migrate the application whenever feature work leaves them time."
Feature work has immediate stakeholders. Modernization usually does not. Predictably, the migration tickets slide from sprint to sprint.
If the work stays internal, protect it formally: name an owner, reserve capacity, and agree on milestones with the business.
Staff augmentation solves a different problem. Suppose the team understands the product but has never run an AngularJS-to-React migration. Bringing in two senior engineers with that specific background can close the expertise gap without moving ownership of the system outside the company. Knowledge transfer happens naturally because the specialists participate in the same repository, reviews, and engineering rituals.
The cost is management attention. Augmented engineers still need decisions, priorities, and access to knowledgeable people. If internal leads are already saturated, adding specialists does not automatically create leadership capacity.
A dedicated external team fits better when the company cannot slow feature development and the migration requires its own throughput. Knowledge retention then has to be designed rather than assumed. Documentation standards, pairing expectations, handover milestones, and responsibility for architectural decisions should be part of the scope from the beginning.
Teams evaluating JavaScript software development companies for this kind of work should look beyond general React or JavaScript credentials. Specific experience with the legacy stack matters. Engineers who have actually dismantled a Backbone or AngularJS application have encountered coexistence problems that greenfield React experience does not teach.
Whichever structure is used, accountability should remain internal. One named engineering owner inside the organization needs to remain responsible for the migration end to end.
The Failure Modes Are Predictable
Most failed modernization programs do not collapse because React failed to render a component. They fail because scope, ownership, or reversibility deteriorates.
Failure mode
Early sign
Practical response
Incremental migration quietly becomes a rewrite
Every migrated area acquires a new design system, state layer, API redesign, or unrelated cleanup
Define what is explicitly outside migration scope before work begins
The hybrid becomes permanent
Two frameworks remain in production for several quarters without retirement dates
Pair every migration task with deletion work and track legacy modules remaining
Stakeholders lose patience
"When do we get features again?" becomes a recurring question
Avoid a complete feature freeze; attach visible product improvement where practical
Legacy knowledge disappears
Original authors leave and answers increasingly begin with "I think…"
Characterize behavior early, particularly in modules owned by people likely to leave
Product work consumes the migration
Migration tickets roll over for three or more sprints
Reserve fixed capacity with an executive sponsor or separate the migration team
Cutover becomes a single high-risk event
The plan depends on one date when everybody switches
Roll out by route or segment and maintain a tested rollback path
The common thread is reversibility.
A migration that can stop after any completed increment and leave behind a coherent, shippable application is much more resilient to budget changes, reorganizations, departures, and shifting product priorities.
A migration that only pays off when 100% complete is much more fragile.
The Netscape rewrite is the famous example, but a more ordinary failure is probably more relevant: a company migrates 40% of the product, priorities change, and the organization spends the next four years supporting both architectures.
Design specifically against that outcome.
A Phased Roadmap That Can Survive Changing Priorities
The calendar cannot be estimated responsibly from an article. A 30,000-line application with clear seams is a different job from a smaller application with tightly coupled state, no tests, and undocumented business rules.
The sequence is more reusable than the duration.
Phase
Focus
Deliverable
Exit criterion
Assessment and build foundations
Dependency audit, module strategy, CI, browser targets
Engineers can use modern syntax and receive useful CI feedback
Characterization and E2E protection around critical paths
A deliberate regression on a top business path fails CI
Module-by-module replacement with legacy deletion
Number of remaining legacy modules is consistently falling
Remove old framework and consolidate dependencies/build
Users receive only the new stack
Phase 2 normally consumes the largest share of the calendar. It is also where competition with feature delivery becomes most intense.
A company that gives the migration a protected track — either through dedicated internal allocation or by choosing to hire experienced JavaScript developers specifically for the work — avoids forcing every migration task to compete with the normal product backlog.
Two planning rules help.
Do not commit to a total duration before Phase 0 has produced enough information to support one. Early estimates made without an architecture and dependency assessment are guesses, and those guesses often become commitments anyway.
And report progress using something countable. "Legacy modules remaining" is more useful than "65% complete." The former can be verified. The latter can hide almost anything.
What Modernization Can — and Cannot — Fix
A successful front-end modernization should make routine changes safer, widen the practical hiring pool, and reduce exposure from unsupported dependencies. Those outcomes are concrete enough to justify the investment on their own.
It does not automatically make a product "AI-ready."
That claim appears frequently in modernization marketing, but for a front-end project it often collapses several unrelated architectural problems into one slogan. AI-driven product features depend far more on data quality, service boundaries, APIs, instrumentation, backend architecture, and the availability of usable domain data.
Replacing a jQuery interface with React does not repair a data model nobody can query.
There is a narrower engineering benefit. AI coding tools work better when a codebase gives them the same things human engineers need: clear modules, types, tests, predictable interfaces, and fast feedback. State of JS 2025 data reported by InfoQ found that 40% of developers were writing exclusively in TypeScript. Moving toward that kind of typed, modular ecosystem can make tooling more effective.
That is an engineering productivity effect, not a new product capability. The distinction matters.
Modernization will not fix organizational bottlenecks either. If feature delivery is slow because requirements change repeatedly, approvals take weeks, or several teams have overlapping ownership, a newer framework simply reaches the same constraint faster.
Those issues should be separated before the business spends money solving the wrong problem.
Start With the Smallest Reversible Step
The framework choice receives disproportionate attention because it is concrete and easy to debate. The sequence of the migration usually matters more.
Assess the system before promising a finish date. Improve the tooling needed to change it safely. Establish protection around the behavior that matters. Prove that old and new code can coexist. Migrate one bounded area, expose it to real traffic, and remove the code it replaces.
Then repeat.
A dependency and support-status audit is a sensible first deliverable because it tells you whether the immediate problem is security risk, development friction, or both. Measuring test protection on the three most commercially important paths tells you whether migration can begin safely or whether the safety net needs attention first.
Only then does the staffing question become concrete: is the limiting factor specialist knowledge, engineering capacity, or both?
React versus Vue, Vite versus Webpack, and Vitest versus Jest all have consequences. None compensates for a migration that cannot survive a changed priority halfway through.
Frequently Asked Questions
What is legacy JavaScript code?
Legacy JavaScript is code that has become expensive or risky to change. Common causes include weak automated test coverage, direct DOM manipulation used as state management, tightly coupled global scripts, poor module boundaries, and unsupported dependencies.
Age alone does not make a JavaScript application legacy. Older code that remains modular, tested, understandable, and maintainable can still be healthy.
Should you rewrite or refactor a legacy JavaScript application?
Incremental migration is the safer default for most medium and large business applications.
A full rewrite becomes easier to justify when the application is genuinely small, its framework is unsupported, requirements exist independently of the current code, coexistence would be disproportionately difficult, the business has explicitly accepted slower feature delivery, and the team has relevant migration experience.
If several of those conditions are absent, the risk profile usually favors incremental replacement.
What is the strangler fig pattern in software modernization?
The strangler fig pattern is an incremental replacement strategy named by Martin Fowler in 2004. New functionality is introduced around the existing system, traffic or functionality moves toward it piece by piece, and the legacy implementation is removed as it becomes unnecessary.
In front-end applications, that boundary can be implemented at the route level, through mount points inside an existing DOM, or with web components.
How do you test legacy JavaScript when there is no existing coverage?
Start with characterization tests that capture existing behavior rather than trying to prove that every behavior is correct.
Protect revenue, authentication, and data-mutating flows first, followed by the module scheduled for migration. End-to-end tests are often quicker to establish on a tightly coupled application because they exercise the running product without requiring every internal module to become testable first.
Is jQuery obsolete?
Not as a category. jQuery 4.0.0 was released in January 2026, and the 4.x branch is supported.
The risk is version-specific. The 1.x and 2.x branches have not received patches since 2016, while a meaningful share of jQuery deployments still uses them. AngularJS 1.x is a clearer end-of-life case because official long-term support ended at the end of 2021.
How long does legacy JavaScript modernization take?
There is no useful universal duration. Codebase size matters, but coupling, test coverage, architectural boundaries, undocumented behavior, and the amount of engineering capacity genuinely reserved for migration can matter just as much.
Complete the initial dependency and architecture assessment before producing a serious estimate. Numbers committed before that work are usually assumptions presented as plans.
Do you need a dedicated migration team, or can staff augmentation work?
Both can work.
Staff augmentation fits when the company has enough internal capacity and domain knowledge but lacks engineers with specific migration or target-stack experience. A dedicated team is more appropriate when product development cannot slow and migration needs its own delivery capacity.
If the internal team already has both expertise and capacity, keeping the work in-house with a protected allocation may be the simplest option.