note
How a Small Angular Directive Unblocked Our Frontend Migration
How a lightweight Angular–Vue integration turned a risky frontend rewrite into a progressive migration that started delivering production value during onboarding.
The challenge
When I joined Everli, the customer-facing e-commerce application was built with Angular and needed to move towards Vue. The difficult part was not choosing the destination. It was finding a safe route from a live product to the new architecture.
A big-bang rewrite was not a practical option. The existing product had to remain stable, normal feature delivery had to continue and the business was growing. Waiting months for the first migration release would have concentrated risk into one rollout while delaying any product value from the work.
The key breakthrough
The main breakthrough was a relatively small custom Angular directive. We used it to let the existing Angular router choose whether a route rendered an Angular view or a Vue page built from the new shared component library.
I built the directive and integration layer that mounted the requested Vue component or page, passed Angular-managed application data and route state into it, handled events emitted by Vue and cleaned up the Vue instance when the Angular view changed.
This was a lightweight framework-coexistence architecture: microfrontend-style in the way two frontend runtimes shared one product, but not an independently deployed microfrontend platform. Angular remained the application shell and Vue entered through an explicit integration boundary.
The historical mechanism can be reduced to something like the example below. It is intentionally simplified and describes a transitional solution, not a reusable modern recommendation.
app.directive('everliComponentWrapper', function () {
return {
scope: { everli: '=' },
link(scope, element) {
if (!scope.everli) return
if (window.everliInstance && !scope.everli.keepalive) {
window.everliInstance.$destroy()
}
window.everliInstance = createVueInstance({
element,
components: scope.everli.components,
data: scope.everli.data,
route: scope.everli.route,
onEvent: scope.everli.onEvent
})
}
}
})Immediate delivery during onboarding
The value of this architecture appeared immediately. We did not have to wait for the complete application to be rewritten before shipping Vue to production. Small views and complete Vue pages could be developed, reviewed and released behind routes that the existing Angular application already understood.
The migration started delivering production value during my onboarding rather than after the entire application had been rewritten. Each release exercised the integration boundary with real product behaviour, which reduced the amount of untested change accumulating outside production.
A component-driven workflow
Alongside the integration layer, we created a separate Vue component library with a Vue CLI library build and Storybook. It contained reusable building blocks as well as page-level components that Angular could request through the wrapper.
The workflow included unit tests, Vuex store tests where state justified them, snapshot tests, documented inputs and behaviour, and mocked API states. Storybook gave design and engineering a shared place to review components interactively before they were assembled into a product route.
This mattered because the migration was not only a framework replacement. The component library, test setup and design review process defined how new work could be delivered consistently while two frontend architectures coexisted.
Progressive migration
We migrated customer-facing sections one at a time. Angular rendered fewer views with each stage, while Vue moved from isolated components to owning complete screens. Eventually Angular was used mainly for routing and application-shell responsibilities.
- Angular application
- Angular router rendering Angular views or Vue pages
- Vue owning most customer-facing sections
- Angular remaining mainly as router and shell
- Nuxt taking over application responsibilities
- Angular fully retired
The sequence was more important than any individual framework decision. Every intermediate stage was a valid production architecture with a clear next step, rather than a half-finished rewrite waiting for a single launch.
Moving to Nuxt
Once Angular itself became the main remaining constraint, we introduced Nuxt and moved application responsibilities progressively: routing, page components, session-related logic, Vuex modules and other application state, configuration, test setup and Storybook integration.
For a transitional period, the Nuxt repository could run as the future standalone application while still generating the component-library build consumed by Angular. That dual role kept normal production delivery moving while we prepared the final shell migration.
Ownership moved with the code. Angular did not disappear before Nuxt could take responsibility for the behaviour around each migrated page, and the temporary integration remained explicit enough to remove when its purpose was complete.
Completing the modernisation
The original Angular frontend and the transitional Angular–Vue integration were eventually retired. The customer-facing application now runs on a consistent Nuxt, TypeScript and Tailwind CSS stack.
That later rebuild was another architectural stage, not evidence that the transition had failed. The coexistence layer had already done its job: it allowed us to release useful work while moving the product away from Angular without a single high-risk cutover.
Lessons learned
- Define a transition architecture, not only the target architecture.
- Make intermediate migration stages releasable.
- Accept temporary complexity deliberately and give it an exit plan.
- Move application ownership progressively, together with the code.
- Treat testing, Storybook and design collaboration as part of the migration architecture.
- Optimise for continued product delivery rather than framework purity.
The migration succeeded because we converted one large rewrite into a sequence of small, releasable changes. The custom directive was modest infrastructure, but it gave the team the transition architecture needed to make progress in production from the beginning.