Lessons Learned from a Progressive Migration with Web Components
Tobias Kohr
Deprecation is a familiar challenge in the world of software. Frontend technologies evolve quickly, and frameworks that once shaped the industry eventually reach their end of life. When this happens, teams are left with the risks that come with aging technology. Support fades, compatibility becomes uncertain, and maintaining the system grows harder and slower.
AngularJS 1 is a good example. When it appeared in 2010, it changed how many teams approached frontend development. Official support ended in 2022, yet many long-lived platforms still rely on it. The key question becomes: how do you move forward without stopping everything?
One of the platforms facing this situation was the Geoportal of Luxembourg, an advanced application based on Geomapfish, OpenLayers, and AngularJS. Over the years, it had grown rich in features and deeply customised. The development environment had become slow and heavy, in part due to the complexity of its Docker composition. A complete rewrite was not realistic, since the production system continues to evolve with new features and fixes. We needed a progressive migration that would let us modernise step by step while keeping the platform alive.
We approached the work in several phases.
Phase 1: Starting small with LitElement
Our first step was to introduce LitElement web components directly into the existing application. We kept the original architecture almost untouched. We kept the LESS-based CSS and avoided any large changes to the global state of the application. This proved that the integration was technically possible, but the experience was not satisfying. Development stayed slow, and the strong coupling with legacy code made even small changes feel heavy. It became clear that simply embedding Lit components inside the old framework would not provide the improvement we needed.
Phase 2: Creating room to grow with RxJS
The second phase focused on gaining more freedom. We created a separate application and repository for developing new web components. This removed the constraints of the legacy system and made the work immediately faster and smoother.
At the same time, we started building a new application core with state management based on RxJS and introduced Tailwind CSS for styling. Lit and RxJS worked well together, but the combination was verbose and not widely adopted. It worked, yet it did not feel like the most sustainable direction.
Phase 3: Moving to Vue and Pinia
In the third phase, we shifted from Lit and RxJS to Vue and Pinia. We also developed a new state persistor to synchronise the application state with both the URL and local storage.
The migration went more smoothly than expected. Vue’s reactivity system provided what Lit lacked: built in reactivity, with fewer layers of boilerplate. Patterns were clearer and easier to maintain. Vitest also helped simplify testing.
Integration: A hybrid model to keep moving
Once the new core and components were stable, we packaged them as a library and built a hybrid environment where they could run alongside the legacy application. This allowed us to replace parts of the old frontend progressively without interrupting production work.
The new core made integration easier. The components worked well together. Legacy parts could still listen to the new state, and the OpenLayers map object continued to flow into the older code, which kept existing tools operational.
There were some friction points. For example, Vue web components do not provide a simple way to disable the shadow DOM, which meant adjusting some CSS on both sides. These were manageable issues, though, and they did not slow the migration significantly.
The hybrid approach turned out to be a strong strategy. We could build the new application in parallel while integrating parts of it into the live system at a sustainable pace.
Conclusion
Looking back, a few lessons stand out.
Starting with small experiments helped build confidence, but real progress began only when we separated new development from the legacy environment. A progressive migration does not have to be disruptive. By layering new technologies carefully and keeping the old system running long enough to hand over responsibilities, we created a smooth path forward for both developers and users.
The hybrid model demanded extra work, but it gave us the flexibility we needed. It allowed us to modernise the platform step by step without blocking ongoing development or risking a major break. Technically, several combinations were possible. LitElement and RxJS worked, but they created more noise. The move to Vue and Pinia offered a cleaner, more productive development experience.
This approach helped us modernise a complex application while respecting its history, its users, and the pace of real-world operations.
Career
Interested in working in an inspiring environment and joining our motivated and multicultural teams?