
Zero‑Downtime Angular Upgrade: 11 → 20 in 6 Weeks — RxJS 8, TypeScript 5, Vite, and a 41% Bundle Cut
A real upgrade story from a leading telecom analytics dashboard: step‑by‑step timeline, breaking changes handled, and measurable performance wins—without taking production down.
“Six weeks. Zero downtime. 41% smaller bundles and a 58% faster build pipeline—while the dashboard stayed live through peak sports traffic.”Back to all posts
I’ve done a lot of Angular upgrades for Fortune 100 teams. The one below—an 11→20 jump for a leading telecom’s advertising analytics dashboard—was the cleanest: six weeks, zero downtime, and a 41% bundle reduction. Here’s the exact timeline, the breaking changes we tackled, and the performance we measured.
The Night the Graphs Stuttered: Why We Upgraded From Angular 11 to 20 Without Downtime
Challenge
On a Thursday night during a playoff game, the charts on a telecom advertising dashboard jittered under peak load. I’d built similar real‑time dashboards for a broadcast media network and an airline ops center—I knew the fix wasn’t another debounce. It was time to modernize: Angular 11 to 20, but production couldn’t blink.
Spiky traffic during live sports drove CPU to 85%
Bundle bloat and long cold starts from Webpack
RxJS and TypeScript drift blocking new features
Constraints
We had to maintain enterprise SSO, audit trails, and multi-tenant isolation. The UI leaned on PrimeNG components with custom tokens—breaking those would create costly retraining issues. We chose a parallel upgrade track with canary traffic and aggressive observability.
Zero downtime, enterprise SSO, and strict audit logs
PrimeNG-heavy UI with custom theming
Multi-tenant roles and ABAC permissions
Project Context: Advertising Analytics at Telecom Scale
What we were upgrading
The app ingested billions of events/day, summarized spend and reach across campaigns, and streamed WebSocket deltas to PrimeNG tables/charts. The codebase was stable but dated. CI ran on GitHub Actions; hosting and previews were on Firebase Hosting with Cloud Functions for APIs.
Angular 11, Nx 12, RxJS 6.x
Webpack build, TSLint, TS 4.1
PrimeNG 11, custom theme, D3/Highcharts mix
Goals we set with leadership
Stakeholders care about reliability and speed. We made performance budgets visible in CI and agreed on a sunset for dead code once tracking proved no regressions.
Zero downtime and steady conversion rates
<2s p95 LCP on core dashboards
Cut build times by 50% and reduce bundle >30%
Timeline: Six Weeks, Zero Downtime
Week 1 — Audit and safety rails
We started with a dependency diff and set up a parallel CI lane that built both the current (prod) and the upgrade branch. A Firebase canary channel received 5% of traffic from internal users. Cypress smoke ran on both lanes.
Dependency inventory and compatibility map
Two-lane CI: current vs next
Firebase canary channel with 5% traffic
Week 2 — 11→12→13 and ESLint migration
We upgraded one major at a time, merging fixes only when tests passed. ESLint caught a few implicit anys and zone-unaware tests.
ng update across majors with tests green each step
TSLint→ESLint using @angular-eslint schematics
Fix strict templates and any types
Week 3 — 14→15→16 and esbuild
Build times dropped immediately with esbuild. We added a theme shim so PrimeNG tokens wouldn’t break visual consistency.
Switch to esbuild builder (v16) for faster builds
PrimeNG to 15 with token shims
RxJS 7 codemods, begin deprecating toPromise
Week 4 — 17 and Vite builder
Vite improved HMR for the team and shaved ~30% off prod bundles. TS 5 required moduleResolution: bundler and path alias fixes.
Migrate to new Angular builder (Vite under the hood)
Update TypeScript to 5.x; fix moduleResolution
Rebaseline budgets and source maps
Week 5 — 18→19→20 and RxJS 8
We closed the loop on RxJS 8 and re-tuned a few retry strategies for WebSocket reconnects used in the live dashboards.
RxJS 8 finalization (firstValueFrom, tap import paths)
Audit deprecated APIs, enable optional inject()
Re-run e2e with network throttling
Week 6 — Hardening and promotion
We scaled canary traffic while watching GA4, Firebase logs, error budgets, and Angular DevTools render counts. With clean signals, we promoted to 100% without a blip.
Load tests, Lighthouse/INP sweeps, a11y pass
Promote canary to 25%→50%→100%
Post-cutover watch with automatic rollback
Breaking Changes We Navigated from Angular 11 to 20
// before (Angular 11 / RxJS 6)
const data = await http.get<Report>(url).toPromise();
// after (Angular 20 / RxJS 8)
import { firstValueFrom, retry, map } from 'rxjs';
const data = await firstValueFrom(
http.get<Report>(url).pipe(
retry({ count: 3 }),
map(r => ({ ...r, loadedAt: Date.now() }))
)
);# Stepwise majors with tests at each hop
npx nx run-many -t test --all
ng update @angular/core@12 @angular/cli@12 --force
ng update @angular/core@13 @angular/cli@13 --force
# ... iterate up to 20
ng update @angular/core@20 @angular/cli@20 --forceAngular CLI → esbuild → Vite
We moved from Webpack to esbuild in v16, then to Vite in v17+. Both required revisiting budgets and source maps, and validating lazy-loaded routes.
16: esbuild builder
17+: Vite builder with faster prod bundles
TypeScript 5.x
TS 5 forced us to clean up a few legacy types. Result: clearer contracts for chart adapters and websocket services.
moduleResolution: bundler
Decorator metadata changes surfaced hidden any
RxJS 8
We applied codemods and audited async/await usage in effects and resolvers. Example:
toPromise removed; use firstValueFrom/lastValueFrom
Operator imports updated
Implementation Walkthrough: Branch Strategy, CI, and Safe Releases
# .github/workflows/upgrade-canary.yml
name: Upgrade Canary
on:
push:
branches: [upgrade/angular20]
jobs:
build-test-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: corepack enable && pnpm i --frozen-lockfile
- run: pnpm nx affected -t lint test build --parallel=3
- run: pnpm nx run web:build --configuration=production
- name: Deploy canary
run: pnpm firebase deploy --only hosting:canary --project telecom-analytics// angular.json (snippets)
{
"projects": {
"web": {
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser-esbuild", // v16
"options": { "budgets": [{"type":"initial","maximumWarning":"1.8MB","maximumError":"2.2MB"}] }
}
}
}
}
}// After Angular 17+
{
"projects": {
"web": {
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser", // Vite under the hood
"options": { "budgets": [{"type":"initial","maximumWarning":"1.4MB","maximumError":"1.8MB"}] }
}
}
}
}
}Two-lane CI and canaries
The canary channel ran behind SSO so only internal traffic hit it. We wired GA4 to segment users by build ID and watched Core Web Vitals before promotion.
Build/test current and upgrade branches in parallel
Deploy upgrade to Firebase canary channel
Angular builders and budgets
We updated the builder progressively and re-baselined budgets to reflect Vite’s chunking.
angular.json: esbuild then Vite
Budgets to catch regressions
ESLint and quality gates
Static analysis prevented a lot of churn during rapid major bumps.
@angular-eslint, strict templates, codelyzer rules replaced
Pre-commit + CI lint
UI and Theming: PrimeNG Changes Without Retraining
/* theme-shim.scss */
:root {
--brand-primary: #2b6cb0; // old $brand-primary
--surface-1: #0f172a;
}
.p-button.p-button-primary { background: var(--brand-primary); }
.p-datatable .p-datatable-thead > tr > th { background: var(--surface-1); }PrimeNG token shims
We preserved look-and-feel while moving to modern PrimeNG. We also added Storybook snapshots to catch theming drift.
Mapped legacy SCSS vars → CSS vars
Kept density/spacing identical
A11y polish ride-along
Small accessibility wins came “for free” while we touched components, something I push on every engagement.
Color contrast AA, focus rings, table semantics
Performance Gains: Before vs After
import { signal, computed } from '@angular/core';
export class FilterStore {
private readonly _region = signal<string | null>(null);
private readonly _campaign = signal<string | null>(null);
readonly params = computed(() => ({
region: this._region(),
campaign: this._campaign(),
}));
setRegion = (v: string | null) => this._region.set(v);
setCampaign = (v: string | null) => this._campaign.set(v);
}What we measured
We treat performance like a feature. Results below are from the same dashboards under realistic traffic.
Angular DevTools render counts on hot tables
Lighthouse, p95 LCP/INP, error budgets
Bundle sizes and build times
Results
Signals/SignalStore came in after the version upgrade for the busiest filter panel. Here’s a simplified pattern we used:
41% main bundle reduction (2.4MB → 1.41MB)
Build times down 58% (11m → 4m 36s)
p95 LCP 3.2s → 1.9s; p95 INP 220ms → 110ms
Render counts -34% after introducing Signals in filters
How an Angular Consultant Approaches Signals Migration Post-Upgrade
Why post-upgrade
We avoid coupling framework upgrades with state rewrites. After v20 was stable, we migrated filters and data-intensive widgets to Signals/SignalStore.
Reduce risk: separate versioning from state refactors
Target hot paths first
Guardrails
We sample render counts and use feature flags to compare RxJS-based selectors vs signal-based computed values. Promotion happens only when metrics improve.
Angular DevTools flame charts and render counts
Feature flags around store usage
When to Hire an Angular Developer for Legacy Rescue
Common triggers
If you need a senior Angular engineer who has upgraded Fortune 100 apps under load, bring someone in early. As an Angular consultant, I pair with your team, keep delivery moving, and leave you with guardrails you’ll keep using.
Budget pressure but growing performance debts
Blocked by RxJS/TS/CLI drift
Production incidents tied to build tooling
Takeaways and Next Steps
Key lessons
This upgrade finished in six weeks without downtime, cut the main bundle by 41%, halved build times, and improved Core Web Vitals. If you’re planning an Angular 20+ roadmap and want a steady hand, I’m available as a remote Angular contractor.
Do majors one at a time with tests green at every hop.
Use canary channels and telemetry; promote when clean.
Move to esbuild/Vite early to unlock big wins.
De-risk state refactors; adopt Signals after the upgrade.
Measure everything; publish the wins.
Key takeaways
- Upgrade majors incrementally (11→12→…→20) behind canaries and traffic splitting to avoid downtime.
- Tackle breaking changes in isolation: RxJS 8 (toPromise→firstValueFrom), TypeScript 5 moduleResolution, ESLint migration, PrimeNG theming.
- Move from Webpack to esbuild (16) then Vite (17+) for faster builds and smaller bundles.
- Introduce Signals/SignalStore post-upgrade on hot paths to reduce renders before fully going zoneless.
- Instrument everything: DevTools render counts, Lighthouse, Core Web Vitals, GA4/Firebase logs, and error budgets.
Implementation checklist
- Inventory dependencies and lock versions by major (Nx, Angular CLI, PrimeNG, RxJS).
- Create an upgrade branch with CI matrices (current vs next) and canary deploys.
- Run ng update per major; fix tests before proceeding.
- Migrate TSLint→ESLint, RxJS 7→8 codemods, TS 5 config changes.
- Switch to esbuild (v16) then Vite builder (v17+) with budgets and source maps verified.
- Smoke test critical flows (auth, tables, exports) with Cypress on every canary.
- Roll out feature slices behind route-level gates; promote when telemetry is clean.
Questions we hear from teams
- How long does an Angular 11 to 20 upgrade take?
- For a mature app with solid tests, expect 4–8 weeks. This case took six weeks with stepwise majors (11→12→…→20), parallel CI lanes, and canary releases. Smaller apps can move faster; complex monorepos can take longer.
- What does zero downtime mean for an Angular upgrade?
- Users never see a maintenance page. We ship to a canary channel, route a small traffic slice, watch telemetry (LCP/INP, errors), then gradually promote to 100%. Automatic rollback is ready if budgets breach.
- What are the biggest breaking changes from Angular 11 to 20?
- RxJS 8 (toPromise removal, import path updates), TypeScript 5 moduleResolution changes, builder shifts to esbuild/Vite, and TSLint→ESLint. UI libraries like PrimeNG/Material may require theme and component API updates.
- Do we need to adopt Signals during the upgrade?
- Not required. I typically decouple framework upgrades from state refactors. Stabilize on v20 first, then migrate hot paths to Signals/SignalStore to capture render and UX wins with less risk.
- How much does it cost to hire an Angular developer for an upgrade?
- It varies by scope and test coverage. Typical engagements range from a 2–4 week assessment to a 4–8 week full upgrade. I offer fixed-fee assessments and weekly rates for delivery. Discovery call within 48 hours.
Ready to level up your Angular experience?
Let AngularUX review your Signals roadmap, design system, or SSR deployment plan.
NG Wave
Angular Component Library
A comprehensive collection of 110+ animated, interactive, and customizable Angular components. Converted from React Bits with full feature parity, built with Angular Signals, GSAP animations, and Three.js for stunning visual effects.
Explore Components