Modernizing a 15-Year-Old .NET System Without Breaking Production (Part 5)

Boosting an AngularJS Application with Vue (Without a Big Bang Rewrite)

Series: Modernizing a 15-Year-Old .NET System Without Breaking Production
Part 5 of 7


The frontend was built on AngularJS (1.x): stable, familiar, and working well in production. But it was increasingly hard to evolve:

  • the ecosystem is effectively frozen
  • onboarding new developers became painful
  • modern tooling and TypeScript support is limited
  • UI complexity grew faster than the framework could comfortably handle

A rewrite was too risky. So we modernized incrementally.

The Strategy: Run AngularJS and Vue Side-by-Side

We kept AngularJS as the foundation and introduced Vue 3 for new screens, running both frameworks simultaneously in the same application. The approach centered on clean architectural boundaries:

  • New screens: Vue 3 with Composition API
  • Legacy screens: AngularJS 1.8.2
  • Shared infrastructure: Authentication, session management, and API client
  • Clear ownership: Route-level separation with explicit boundaries

Dual Routing Architecture with TypeScript

We implemented a sophisticated routing strategy using Vue Router 4 alongside AngularJS routing, with full TypeScript support:

const routes: RouteRecordRaw[] = [
  { path: '/v', component: TheVueWrapper, children: [/* Vue routes */] },
  { path: '/:pathMatch(.*)', component: TheAngularWrapper } // Fallback
]

This allows Vue to handle modern routes while AngularJS catches everything else, enabling gradual migration without disrupting existing functionality. TypeScript provides compile-time route validation and autocomplete support.

Framework Wrappers: The Integration Layer

Two key wrapper components manage the framework boundary:

TheVueWrapper.vue – Hosts all Vue 3 pages with modern infrastructure:

  • PrimeVue components for rich UI
  • Pinia stores for state management
  • Composition API with “
  • Shared header, sidebar, and navigation
  • Full TypeScript type safety throughout

TheAngularWrapper.vue – Embeds the entire AngularJS application:

  • Bootstraps AngularJS on mount: angular.bootstrap(container, ['legacyApp'])
  • Properly tears down scopes on unmount to prevent memory leaks
  • Synchronizes route changes between frameworks
  • TypeScript types for Angular scope and lifecycle management

This architecture means Vue Router controls top-level routing, delegating to AngularJS when needed, with TypeScript ensuring type safety at the boundary.

The Technology Stack

Our Vue 3 implementation uses modern tooling:

  • Build: Vite 5 with lightning-fast HMR and optimized production builds
  • Language: TypeScript 5 with strict mode enabled
  • UI Framework: PrimeVue 4 with custom theme
  • State Management: Pinia with TypeScript stores
  • Styling: Tailwind CSS + SCSS
  • Dev Experience: ESLint, Prettier, instant hot module reloading
  • Testing: Vitest for unit tests with component isolation

Why Vite?

Vite transformed our development experience:

Instant Server Start: Cold start in ~200ms vs 15+ seconds with Webpack

Lightning HMR: Changes reflect in under 50ms, preserving application state

Optimized Builds: Rollup-based production builds with automatic code splitting

Native ESM: No bundling in development, just native ES modules

Simple Configuration: Minimal vite.config.ts vs complex Webpack setup

export default defineConfig({
  plugins: [vue()],
  resolve: { alias: { '@': resolve(__dirname, './src') } },
  build: {
    rollupOptions: {
      output: { manualChunks: { vendor: ['vue', 'vue-router', 'pinia'] } }
    }
  }
})

TypeScript Integration

Full TypeScript coverage brings significant benefits:

Type-Safe Stores: Pinia stores with interfaces for state, getters, and actions

interface UserState {
  currentUser: User | null
  permissions: Permission[]
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({ currentUser: null, permissions: [] }),
  getters: {
    hasPermission: (state) => (perm: string) => 
      state.permissions.some(p => p.name === perm)
  }
})

Type-Safe Components: Interface-based props, typed emits, and composables with full inference

interface Props {
  itemId: number
  status?: 'pending' | 'active' | 'completed'
}

const emit = defineEmits()

The build process outputs to /dist with optimized, tree-shaken bundles and automatic code splitting for optimal performance.

Migration Progress

We’ve migrated 50+ module areas to Vue 3, including:

  • Data grids: Complex tables with sorting, filtering, and pagination
  • Forms: Multi-step wizards with validation
  • Dashboards: Real-time data visualization
  • Reports: Dynamic report generation with export capabilities
  • Settings: Configuration management screens
  • User management: Authentication and authorization flows

Each Vue screen lives under the /v/ route prefix, while legacy AngularJS screens remain at their original routes. All new code is written in TypeScript with full type coverage.

Why This Worked

Low risk: No “flag day” cutover. Both frameworks run side-by-side in production.

Fast wins: New features ship faster using modern Vue 3 + Composition API + TypeScript patterns.

Better DX: New developers work exclusively in Vue with familiar tooling:

  • Vite’s instant HMR (sub-50ms updates)
  • TypeScript autocomplete and type checking
  • Modern component patterns with “
  • Vitest for fast, Jest-compatible testing

Gradual retirement: AngularJS code shrinks organically as features are rewritten when touched.

Performance: Vite’s optimized builds with automatic code splitting means users only download what they need. Initial bundle is small; features load on-demand.

Key Lessons

Don’t share UI state between frameworks. We use a shared API client and Pinia stores on the Vue side. AngularJS services remain isolated. Communication happens through route navigation and query parameters.

Keep boundaries explicit. Route-level separation is enforced. A view is either Vue or AngularJS, never both. This prevents the “partial migration” trap.

Router is king. Vue Router owns the routing layer, but respects AngularJS routes via the catch-all pattern. This gives us central control while preserving backward compatibility.

Lifecycle management matters. Properly bootstrapping and tearing down AngularJS is critical to prevent memory leaks when navigating between frameworks.

Make “new work goes to Vue” the default. We established a clear policy: all new features use Vue 3 + TypeScript. AngularJS is maintenance-only. This creates momentum toward full migration.

Invest in wrapper quality. Our wrapper components handle complex lifecycle scenarios, authentication state sync, and error boundaries. This investment paid off immediately.

TypeScript strictness pays off. Enabling strict mode (strict: true, noUncheckedIndexedAccess: true) caught countless bugs during development.

Vite’s speed enables experimentation. Instant feedback loops mean developers try more approaches, resulting in better solutions.

The Path Forward

This incremental approach delivered modernization benefits while maintaining production stability. We’re now running a hybrid application in production with:

  • AngularJS handling legacy screens (shrinking footprint)
  • Vue 3 + TypeScript powering all new development
  • Vite providing exceptional developer experience
  • Shared authentication and API infrastructure
  • Type-safe boundaries between old and new code
  • No user-visible distinction between frameworks

Technical Metrics

The migration delivered measurable improvements:

  • Build time: 2 minutes → 8 seconds (93% reduction)
  • HMR speed: 3-5 seconds → 50ms (98% faster)
  • Bundle size: 2.1MB → 680KB (68% smaller)
  • Type coverage: 0% → 100% in new code
  • Development cold start: 15s → 0.2s (98% faster)

The migration continues screen-by-screen, with no pressure for a risky “big bang” rewrite. When complete, we’ll remove the AngularJS wrapper and have a pure Vue 3 + TypeScript application—but that timeline is flexible because the hybrid state is stable and productive.

Key Takeaways

Vite + TypeScript is the modern baseline. The combination provides incredible DX with strong safety guarantees. There’s no reason to use JavaScript without types or slow bundlers anymore.

Incremental migration works. You don’t need to rewrite everything. Build the bridge, cross it gradually, and burn it behind you.

Developer experience drives quality. Fast feedback loops (Vite) + type safety (TypeScript) = fewer bugs, faster development, happier developers.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top