
Upgrading to Angular 20+: Survive CLI/Vite, TypeScript 5.x, and RxJS 7.8 Breaking Changes Without Downtime
A field-tested playbook to navigate Angular CLI, TypeScript, and RxJS breaking changes—protect production, keep velocity, and measure wins.
Green CI is not a release strategy. Upgrades succeed when you control blast radius.Back to all posts
Jenkins says green. You promote, traffic hits, and a harmless “perf” upgrade turns into a live incident: dev server proxy rules don’t map to Vite, toPromise vanished, and TypeScript 5.x tightened a type your error handler relied on. I’ve lived this across airlines, telecom analytics, and insurance telematics. The fix isn’t heroics—it’s guardrails.
As companies plan 2025 Angular roadmaps, the risky bits aren’t Signals or PrimeNG themes—it’s the surface area where Angular CLI (now Vite/esbuild), TypeScript 5.x, and RxJS 7.8+ intersect. Here’s the platform-and-delivery plan I use to ship Angular 20+ upgrades with zero downtime and measurable wins.
The Night It Breaks—and Why It Matters in Angular 20+
If you treat an upgrade as “bump, build, ship,” you’ll ship surprises. Treat it as a release with blast-radius control: metrics, canaries, rollbacks, and targeted codemods. This is where a senior Angular consultant earns their keep. As a remote Angular developer, I’ve done this for Fortune 100 teams without freezing delivery.
A real upgrade failure I’ve seen (and fixed)
During an airport kiosk upgrade (12 → 20), prod kiosks booted into a white screen because a dev-only proxy wasn’t mirrored in the Vite server config, and toPromise was still lingering in an offline flow. We recovered with a Firebase canary rollback in minutes—but only because we had it rehearsed.
What changed between 12–20 that bites teams
CLI now leans on Vite/esbuild; dev proxy and SSR glue changed.
TypeScript 5.x enforces ESM and new module resolution behavior.
RxJS 7.8 removes toPromise and tightens operator typings.
Strict template/type checks and Standalone APIs surface latent issues.
Node 18/20 LTS required in CI; older tooling silently misbehaves.
Preflight Guardrails Before You Touch ng update
Guardrails turn unpredictable upgrades into boring releases. If your plan doesn’t include a one-click rollback and clear SLOs, it’s not ready.
Freeze and snapshot
git tag vBeforeUpgrade and push.
Commit lockfile and engines field.
Snapshot env, SSR, and build flags in CI.
Measure and monitor
Lighthouse CI budget: LCP, INP, CLS.
Angular DevTools profiler baseline flame chart.
GA4 + Firebase Logs: error rate, slow endpoints.
Feature flags wired (Firebase Remote Config or LaunchDarkly).
Canaries and rollbacks you can trust
With canaries I shipped a telecom analytics app’s 16 → 20 upgrade while reducing cold build from 7m → 3m and improving LCP by 22%—no maintenance window.
Preview to Firebase Hosting channels per PR.
Automate promote/demote within CI.
Keep a kill switch for any new SSR/streaming path.
Handling Angular CLI and Vite Builder Breaking Changes
{
"projects": {
"app": {
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": "dist/app",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": ["zone.js"],
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"budgets": [{"type":"initial","maximumWarning":"500kb","maximumError":"2mb"}]
}
},
"server": {
"builder": "@angular-devkit/build-angular:server",
"options": { "outputPath": "dist/app-server", "main": "src/main.server.ts" }
}
}
}
}
}Dev server proxy behavior differs with Vite. Keep an explicit proxy config and verify auth/header passthrough in dev to avoid “works on my machine” moments.
Switch to application/SSR builders deliberately
Angular 20 projects use the application builder with Vite for dev. Confirm builders and options in angular.json; don’t rely on defaults after a major bump.
angular.json snippet (browser/SSR)
Below is a safe baseline I use post-upgrade. Note: verify budgets, assets, and fileReplacements for each configuration.
TypeScript 5.x Migration Tactics for Angular 20+
Type correctness is a feature. Tighten it early so RxJS fixes don’t get muddled with template errors later.
tsconfig settings that bite (fix them first)
Align with CLI defaults, then tighten. If libraries or decorators behave oddly after the bump, these flags are the usual suspects:
tsconfig.json baseline
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"useDefineForClassFields": false,
"strict": true,
"noImplicitOverride": true,
"declaration": false,
"types": ["node"],
"paths": { "@app/*": ["src/app/*"], "@shared/*": ["src/app/shared/*"] }
},
"angularCompilerOptions": {
"strictInjectionParameters": true,
"strictTemplates": true
}
}ESM-only dependencies and path aliases
Prefer ESM builds; avoid require in server code.
Check package.json exports fields; TS bundler resolution follows them.
Update ts-node/jest configs for ESM or run tests with Vitest/Web Test Runner.
Template types surface real issues
On a device management portal, strictTemplates flagged a latent undefined in a PrimeNG dropdown that only hit French locales. TypeScript 5.x caught it in CI; prod never saw it.
Standalone + Signals narrow types in templates.
Fix any $any casts; add explicit generics to Observables.
Lean on Angular DevTools template profiler to confirm no extra change detection churn.
RxJS 7.8+ Breaking Changes You’ll Actually Hit
RxJS 7.8’s changes make error handling honest. Embrace typed errors, consistent return types, and lifecycle-aware subscriptions. Your future self will thank you.
toPromise removal → firstValueFrom/lastValueFrom
// Before
const token = await api.refreshToken().toPromise();
// After (with timeout + typed errors)
import { firstValueFrom, timeout, catchError, throwError } from 'rxjs';
const token = await firstValueFrom(
api.refreshToken().pipe(
timeout(4000),
catchError(err => throwError(() => new Error(`refresh-fail:${err?.message || err}`)))
)
);Typed catchError and return types
// Ensure catchError returns Observable<T>
getData(): Observable<Item[]> {
return this.http.get<Item[]>(`/api/items`).pipe(
retry({ count: 2, delay: 250 }),
catchError((e: unknown) => {
this.telemetry.error('items-load', e);
return of([] as Item[]); // satisfy T
})
);
}Prefer takeUntilDestroyed over manual Subject
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
constructor(destroyRef: DestroyRef, private store: SignalStore) {
this.store.items$ // if you still have streams
.pipe(takeUntilDestroyed(destroyRef))
.subscribe(items => this.itemsSignal.set(items));
}PrimeNG, Theming, and Visual Regressions During the Upgrade
Don’t let a technical upgrade turn into a UX regression. Bake visual checks into the same pipeline.
Token/density shifts show up as layout bugs
I pair PrimeNG upgrades with visual testing to avoid “pixel drift.” In a broadcast media scheduler, this caught a density change in p-table that would have added 3 extra scrolls per shift.
Audit PrimeNG version jump alongside Angular.
Lock design tokens; run visual snapshots in CI.
Respect prefers-reduced-motion on upgraded animations.
End-to-End Upgrade Plan With Nx and CI/CD
Shipping is a habit. The more repeatable your upgrade pipeline, the faster future upgrades become.
Run migrations progressively (Nx + Angular CLI)
# preview and apply Angular migrations
npx ng update @angular/core@20 @angular/cli@20 --migrate-only --force
# orchestrate workspace changes with Nx
npx nx migrate latest
npm install
npx nx migrate --run-migrationsCI: Node, caching, and preview channels
name: build-test-canary
on: [push]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- run: npm run test:ci
- run: npm run build:ssr
- run: npx lhci autorun --upload.target=temporary-public-storage
- name: Deploy preview
run: npx firebase deploy --only hosting:app --project myproj --non-interactive --expires 7dGuarded rollout and rollback
In gitPlumbers, this playbook drove a 70% delivery velocity boost and 99.98% uptime across modernization sprints. Contract tests + canaries = courage to merge.
Feature flags wrap SSR/streaming paths.
Promote/demote between Firebase channels in seconds.
Telemetry hooks emit upgrade-specific events (typed schemas).
When to Hire an Angular Developer for Upgrade Triage
If your roadmap can’t tolerate a freeze, bring in a senior Angular expert who has shipped these paths repeatedly.
Signals you need outside help
Green CI, red prod (proxy/SSR/env drift).
RxJS and TypeScript errors tangled together.
Karma/Jasmine legacy, no SSR tests, no canaries.
Multiple apps/packages stuck across versions.
How an Angular consultant de-risks upgrades
I’ve done this for an insurance telematics dashboard (Signals + SignalStore + typed WebSockets), an airline kiosk with Docker-based hardware simulation, and a telecom analytics portal serving millions of events/hour. If you need a remote Angular developer with Fortune 100 experience, I can help.
48-hour assessment: CLI/TS/RxJS diff, env drift, risk register.
One-week plan: codemods, CI updates, lighthouse budgets, rollback tests.
Parallel delivery: features keep shipping behind flags.
Takeaways: Ship Angular 20+ Upgrades Without Surprises
- Start with guardrails: tag, metrics, canaries, rollback.
- Switch builders intentionally; verify dev proxy and SSR.
- Lock TypeScript 5.x configs early; fix strict template issues first.
- Migrate RxJS with codemods and typed errors; remove toPromise.
- Automate with Nx + GitHub Actions; deploy previews; enforce budgets.
- Keep UX safe: PrimeNG tokens/density checked in CI.
Common Commands and Scripts I Actually Use
Keep scripts boring and portable. Your future CI will thank you.
Scripts that survive upgrades
{
"scripts": {
"start": "ng serve",
"build": "ng build",
"build:ssr": "ng build && ng run app:server",
"serve:ssr": "node dist/app-server/main.js",
"test:ci": "ng test --watch=false --browsers=ChromeHeadless --code-coverage",
"lint": "ng lint",
"affected": "nx affected --target=build --parallel",
"migrate": "nx migrate latest && nx migrate --run-migrations"
}
}Questions I Get a Lot About Upgrades
Short answers below; see the FAQ for snippet-friendly versions.
Key takeaways
- Treat upgrades like releases: canaries, rollbacks, metrics, and feature flags—not just green CI.
- Angular 20+ uses Vite/esbuild under the hood—update angular.json builders, SSR config, and dev proxies deliberately.
- TypeScript 5.x tightens types and ESM resolution—set moduleResolution, ensureDefineForClassFields, and fix path aliases early.
- RxJS 7.8 removes toPromise and tightens error types—adopt firstValueFrom/lastValueFrom, typed catchError, and takeUntilDestroyed.
- Automate with Nx and GitHub Actions: affected builds, Lighthouse budgets, and Firebase preview channels to protect prod.
Implementation checklist
- Tag and freeze: git tag pre-upgrade, lockfile committed, prod parity snapshot.
- Baseline metrics: Lighthouse CI, Angular DevTools flame chart, Core Web Vitals in GA4, error rate in Firebase Logs.
- Enable canaries: Firebase Hosting preview channels + feature flags for guarded rollout.
- Bump engines: Node 18/20 LTS in CI, npm/pnpm versions aligned.
- Run ng update with --migrate-only and commit each step.
- Switch builders: application/SSR, verify dev proxy and environment replacements.
- Update TypeScript config: moduleResolution, target, useDefineForClassFields, path aliases, strictTemplates.
- Migrate RxJS: codemods for toPromise, typed catchError, takeUntilDestroyed.
- PrimeNG/Material tokens and theme diffs validated in visual tests.
- Rollback plan rehearsed: one-click promote/demote, config flags as kill switches.
Questions we hear from teams
- How long does an Angular 12–20 upgrade usually take?
- For a typical enterprise app: assessment in 3–5 days, core upgrade path in 2–4 weeks, and hardening/UX checks in 1–2 weeks. Complex monorepos or heavy SSR often land at 6–8 weeks with parallel feature delivery behind flags.
- What breaks most often during Angular 20+ upgrades?
- Dev proxy/SSR behavior with Vite, TypeScript 5.x module resolution and class fields, and RxJS toPromise or loosely typed catchError chains. Visual regressions from PrimeNG/Material token changes are a close fourth.
- How much does it cost to hire an Angular developer for an upgrade?
- Senior Angular consultant engagements typically start at a fixed discovery ($3–5k) and move to a sprint model ($12–18k per two-week sprint) depending on scope, CI/CD maturity, and number of apps/packages.
- Can we avoid a code freeze while upgrading?
- Yes. Use feature flags, Nx affected pipelines, contract tests, and Firebase preview channels. I routinely ship upgrades with zero downtime and no code freeze by isolating risk and rolling out canaries.
- What’s included in your typical upgrade engagement?
- Deprecation audit, CLI/TypeScript/RxJS migrations, PrimeNG/Material theme validation, CI updates (Node 20, Lighthouse, previews), typed telemetry, rollback rehearsal, and a measurable outcomes report (bundle size, LCP/INP, error rate).
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