Modernizing a Vue.js Stack: a migration journey

Raí Siqueira
February 20, 2025

Introduction

When Vue.js announced the end-of-life for Version 2 by the end of 2023, the team faced a critical decision. With three Vue.js applications in production — an internal dashboard, a store locator, and a few web components, we needed a solid strategy to upgrade to Vue 3.

This post details our journey, challenges, and lessons learned during this significant migration.

Why Migration Was Necessary

End-of-Life Concerns

  1. Security vulnerabilities with unsupported Vue 2;
  1. Missing out on performance improvements in Vue 3;
  1. Limited access to new features and community support.

Our Vue.js Ecosystem

  1. Internal dashboard application;
  1. Store locator service;
  1. Web components integrated into Django templates.

Planning the Migration

First and foremost, we needed to understand the scope of the migration, so we created the following plan (as the foundation of the migration strategy):

  1. Creating an Architecture Decision Record (ADR);
  1. Establishing clear objectives and success metrics;
  1. Defining the scope of changes needed.

With the plan in place, we defined the key strategic decisions that would guide the migration process:

  1. Using a phased approach to minimize disruption;
  1. Creating a dedicated Slack channel for knowledge sharing;
  1. Maintaining separate branches for each migration phase;
  1. Isolating projects with their own dependencies.

The Migration Process

The migration was divided into four main phases:

Phase 1: Pre-Migration Work

  1. Updating ESLint rules
  • Adding deprecated API warnings;
  • Implementing stricter code quality checks;
  1. Cleaning up deprecated APIs
  • Removing Vue 2 filters;
  • Updating v-slot syntax.
  1. Improving test coverage

Phase 2: Vue 2.7 Bridge

Since the applications were running on Vue 2.6, we decided to upgrade to Vue 2.7 first to leverage the new features and prepare for the Vue 3 migration.

  1. Updating SCSS rules;
  1. Replacing v-deep syntax;
<!-- Old syntax -->
.parent >>> .child
::v-deep .child
<!-- New syntax -->
:deep(.child)

Phase 3: Core Migration

  1. Package upgrades
  • Vue 2.7 to Vue 3.
  • Vue Router 3 to Vue Router 4.
  • Vuex 3 to Vuex 4.
  1. Build system updates
  • Updating Parcel configuration.
  • At this point we were using Parcel as our bundler (yes, with a Vue 2.6 application). But since Parcel doesn't support Vue 2, we had to write a custom Parcel transform to support Vue 2 components.

Phase 4: UI Framework Migration

  1. Transitioning from Element UI to Element Plus.
  1. Addressing UI inconsistencies.
  1. Updating E2E tests.

Challenges and Solutions

Technical Challenges

  • Managing multiple applications with different Vue versions.
  • Handling breaking changes in UI components.
  • Dealing with test coverage gaps.

Solutions Implemented

  • Project isolation with separate package.json files (monorepo).
  • Comprehensive E2E testing strategy.
  • Progressive feature migration.

Best Practices and Lessons Learned

What Worked Well

  • Using a dedicated migration branch;
  • Knowledge sharing through Slack;
  • Comprehensive pre-migration planning.

What Could Be Improved

  • More thorough test coverage before starting;
  • Better documentation of component changes;
  • Earlier identification of deprecated API usage.

Looking Forward

Future Improvements

  • Leveraging Vue 3's Composition API;
  • Performance optimization opportunities;
  • Enhanced type safety with TypeScript;
  • Remove Parcel as our bundler. We're considering Vite.

Maintenance Strategy

  • Regular dependency updates;
  • Continuous monitoring of deprecations;
  • Proactive technical debt management.

Conclusion

The migration from Vue 2 to Vue 3 was a significant undertaking that required careful planning, execution, and team coordination. While challenging, it has positioned our applications for better performance, security, and maintainability in the future.