Vines gradually replacing a tree illustrating the strangler fig software pattern
9 min readVue 3 Migration

The Strangler Fig Pattern for Vue: Shipping Vue 3 Alongside Vue 2 Without a Big-Bang Rewrite

Big-bang rewrites are high risk and hard to fund. The strangler fig pattern (popularized in microservices) applies to large SPAs: new behavior grows on a new vine (Vue 3) while the old system (Vue 2) is retired route by route or MFE by MFE. You never depend on a single “flag day” unless the business truly requires it.

Typical enablers: a thin top-level shell or proxy router (host app) that can mount sub-apps; @vue/compat for shared leaf components during transition; and a strict rule that all new features start on the Vue 3 path.

1. Strangle by domain, not by “percent complete”

Finish /settings on Vue 3, then /billing, etc. A heatmap of traffic + revenue tells you the order, not a gut feeling from line counts.

2. Shared state at boundaries

Session, auth, and entitlements need explicit contracts. Avoid two silent copies of Pinia/Vuex in memory. Often a small event bus, cookie/session bridge, or shared worker is cleaner than ad hoc window globals.

3. Know when not to strangle

Very small codebases and strict regulatory environments sometimes favor one coordinated release. The pattern is a default, not a religion—compare with timeline reality.

4. Three concrete strangler topologies

“Strangler fig” is a pattern, not an architecture. Pick the topology that matches your traffic, your team count, and how much shell control you actually have. We see three live in production Vue migrations.

A. Reverse-proxy by route

Your edge (NGINX, Cloudflare Workers, Vercel rewrites) routes /billing/* to a Vue 3 build and everything else to the Vue 2 build. Lowest in-app coupling, highest infra responsibility. Excellent first move for content-heavy SaaS where routes are domain-aligned.

# nginx.conf (excerpt)
location ~ ^/(billing|settings)(/|$) {
  proxy_pass http://vue3-app;
}
location / {
  proxy_pass http://vue2-app;
}

B. Shell app + Module Federation

A thin host (often Vue 3 or framework-agnostic) loads remote bundles. Vue 2 and Vue 3 sub-apps register as remotes. Lets two teams ship independently, at the cost of an explicit inter-app contract. See our micro-frontends guide for the failure modes here.

C. In-place with @vue/compat

A single bundle running Vue 3 with the migration build, where individual leaf components still use Vue 2 idioms. Cheapest infra, most painful when third-party plugins disagree on which Vue they target. Best for small apps with tight third-party control.

5. Trade-offs at a glance

TopologyInfra costTeam couplingBundle bloatRollback
Reverse proxyHighLowLowPer-route, fast
Shell + MFEMediumMediumMediumPer-remote
@vue/compatLowHighHighest (dual runtime)Whole-app

If you’re weighing this against the budget, the migration cost estimate changes meaningfully across these. Reverse proxy front-loads infra; @vue/compat front-loads engineering pain.

6. Step-by-step: stranging your first route

  1. Pick a route with low blast radius and clear ownership. Ideally one team, one backend, modest traffic share.
  2. Stand up a Vue 3 app behind the same auth. Reuse session cookies; do not invent a second login.
  3. Move shared types, then shared logic. Composables and API clients ported with types (see typed composables) before any UI is touched.
  4. Cut traffic gradually. Start with internal users, then 5%, 25%, 100% via feature flag or proxy rule.
  5. Delete the old route. The migration is not done until the Vue 2 code path is removed and the bundle shrinks.

Skipping step 5 is the single most common reason a strangler migration becomes “80% done forever.” Schedule the deletion PR in the same sprint as the cutover.

7. Common pitfalls

  • Two routers fighting. Both Vue 2 and Vue 3 try to own history.pushState. Pick one as the outer router and have the other accept handoffs at well-defined paths.
  • Duplicated design tokens. Buttons drift visually between vines. Lift tokens out before the split—our design system extraction post covers this.
  • Bundle bloat that nobody owns. A user on a strangled page downloads both Vue runtimes. Track this in Core Web Vitals dashboards or it silently regresses.
  • Silent state divergence. A user updates their profile on Vue 3, navigates back to a Vue 2 page, and sees stale data. Define a single source of truth and a refresh contract.
  • No deletion ceremony. Old code lingers because deleting it isn’t in anyone’s sprint. Make it a celebrated, named ritual.

8. FAQ

How long should a strangler migration take?

Plan in vines, not in months. A typical mid-sized SaaS strangles 4–8 routes over 2–4 quarters. The realistic timeline guide has size-by-size benchmarks.

Can we strangle Nuxt 2 to Nuxt 3?

Yes, but the SSR layer makes the proxy topology trickier. Many teams use Nuxt Bridge as a transitional vine, not as the destination.

What about shared component libraries?

Use Storybook as the contract layer. Stories run on Vue 3 first; Vue 2 imports the same components via a compat adapter while it is still alive.

When should we not use strangler fig?

Tiny apps (under ~30 components), tightly regulated environments where two simultaneous prod surfaces increase audit cost, or apps where every page is the same. A coordinated cutover is sometimes simply cheaper.

9. What good looks like

  • Each vine has a named owner, a target route, and a deletion ticket already filed.
  • The shell or proxy config is documented, not tribal knowledge.
  • Auth, locale, and feature flags resolve identically on both sides.
  • A monthly bundle-size and Core Web Vitals review compares pre/post-strangle traffic.
  • The Vue 2 footprint shrinks every sprint—if it doesn’t, the migration is paused, not progressing.

If you can produce a one-page diagram showing every active vine and its retire-by date, you’re running a strangler migration. If you can’t, you’re running a parallel rewrite that happens to be incremental.

Conclusion

Strangler delivery turns Vue 2 EOL from a single cliff into a plan with measurable milestones. Name the first vine, fund it, and retire the old path when usage reaches zero—then delete the code.

Related