Context
An enterprise Angular application with 200+ feature modules and ~3 years of accumulated NgModule wiring. Standalone components had matured enough to be the default, and the indirection from NgModules was costing onboarding time and tree-shaking quality.
Constraints
No big-bang rewrites. Feature delivery couldn't pause. Bundle improvements had to be measurable. Existing CI/CD pipeline and lazy-loaded routing had to keep working throughout.
Decisions
Migrate leaf-first: standalone shared components first, then feature components, then routes, then remove the now-empty modules. Introduce a per-PR migration budget instead of a parallel branch. Lock new code to standalone-only via lint rules from day one.
Implementation
Codemod scripts to flip components and to rewrite their consumers. ESLint rule blocking new NgModule declarations. A small dashboard tracking the percentage of standalone files per feature. Routes were migrated last using `loadComponent` to lazy-load directly without intermediate modules.
Outcome
Migration shipped over four sprints with zero feature freezes. Onboarding pages dropped from a 60% NgModule mental model to nearly zero. Lazy-loaded route bundles shrank by 12–18% on average.
Outcome metrics
- Performance
- Lazy route bundles −15% on average
- Maintainability
- NgModule wiring removed from feature code
- Developer experience
- Onboarding time on new features cut roughly in half
- Bundle size
- −15% on lazy chunks