Our build pipeline is taking forever. We have a lot of components and libraries. Are there any architectural changes we can make to speed up the compilation time? I know about lazy loading, but is there something about how our modules are structured that could be slowing down the Ivy compiler?
Angular build performance is improved by reducing deep component dependency chains, eliminating barrel files, and partitioning applications into isolated, buildable libraries with explicit public APIs.
6 answers
The Angular Ivy compiler operates by transforming decorators into static properties, and it performs best when the input codebase exhibits high levels of modularity. When you have a highly interconnected structure of components, the compiler encounters a significant increase in the complexity of the incremental dependency graph. This forces a cascade of re-compilations, as any minor modification to a shared service or component requires the compiler to re-evaluate every downstream dependency linked to that node. To mitigate this, you should adopt a strategy of library isolation, effectively partitioning your application into discrete units that possess clearly defined public APIs.
Furthermore, avoid the common anti-pattern of deep barrel file nesting, as this forces the build tool to index and process large quantities of unrelated files during the module resolution phase. By strictly limiting what is exposed through your entry points, you significantly reduce the amount of work Ivy must perform during the incremental analysis stage. You might also investigate how your tsconfig.json is structured; explicitly mapping paths and keeping the compilation unit small prevents the compiler from scanning the entire root directory repeatedly. The objective is to keep your dependency graph as shallow as possible, which reduces the computational burden on the compiler. If your components are tightly coupled, the compiler is essentially forced to treat your entire application as one giant file, negating the efficiency gains that modern build tools provide for modular systems. Moving toward a more decoupled architecture is not merely about code cleanliness, but is a fundamental requirement for maintaining performant build pipelines in complex web environments.
Your build bloat is likely caused by deep inheritance trees and unnecessary module coupling that forces the compiler to re-evaluate the entire dependency graph on every change. Switch to a monorepo structure with Nx and enforce strict library boundaries to ensure incremental builds actually happen as intended.
I remember dealing with this exact issue back in 2021 when our main banking dashboard hit a tipping point where builds took twenty minutes.
We had a massive shared module that every single component was importing, which forced the Ivy compiler to re-compile everything from scratch whenever someone touched a single label.
By splitting that shared monolith into atomic, feature-specific modules and getting rid of barrel files, we managed to get our CI pipeline down to under four minutes.
You can optimize compilation throughput by isolating your dependency graph and adhering to these architectural constraints:
- Use entry-points to strictly define your public API for each internal library.
- Replace deep component nesting with flattened directory structures to reduce compiler overhead.
- Limit the scope of NgModule declarations to prevent excessive dependency resolution.
- Configure path mapping to avoid relative path hell that triggers full recompilations.
Structuring your application as a collection of independent buildable libraries works effectively if you need parallelization, whereas keeping a single monolithic structure is only performant when your project remains small enough to reside entirely in memory.
The trade-off here is that incremental builds are significantly faster with libraries, but the initial setup cost is higher because you have to manage library versions and explicit dependency linkages across the entire workspace.
Stop using barrel files in your shared modules. They are a performance killer because they force the compiler to resolve every single export in a folder even if you only need one component. Just import directly from the source file and watch your build time drop instantly. Most people bloat their builds because they are lazy with their import paths and don't care about the dependency chain they are creating.