
Lazy Loading in Angular: Optimising Performance

Lazy loading is one of those performance techniques that sounds uncomplicated until we start applying it to a real application. Loading less code up front is clearly good, but the moment we add route transitions, shared dependencies, and loading states, the trade‑offs become more interesting.
Angular gives us strong tools here, especially now that standalone components and route‑level loading fit together cleanly. The challenge isn't whether we can split code, it is whether we're splitting it in ways that reflect real user journeys and keep the application maintainable.
What Lazy Loading is Actually Buying Us
The most obvious gain is a smaller initial bundle. That usually means less JavaScript to download, parse, and execute before the first screen becomes interactive. On constrained devices, that difference can be significant.
It also gives us a cleaner performance budget. If admin tools, reporting pages, and advanced editors don't belong in the first visit, they should not consume the first‑visit budget. Lazy loading lets us express that directly in the route structure.
In data‑heavy products such as Dataffirm, that sort of route‑level separation matters because analytics, reporting, and deeper investigative views do not all need to consume the first‑visit budget.
A Route‑Level Strategy in Angular
import type { Routes } from '@angular/router';export const routes: Routes = [ { path: '', loadComponent: () => import('./features/home/home.component').then((module) => module.HomeComponent), }, { path: 'analytics', loadChildren: () => import('./features/analytics/analytics.routes').then((module) => module.ANALYTICS_ROUTES), }, { path: 'settings', loadComponent: () => import('./features/settings/settings.component').then((module) => module.SettingsComponent), },];This kind of configuration usually provides the clearest return. It keeps less‑frequent sections out of the initial bundle, and it aligns the split points with user‑visible product boundaries. That's much better than scattering dynamic imports at arbitrary component lines without an overall strategy.
When Lazy Loading Can Hurt
There's a temptation to think that more chunking always means better performance. It doesn't. If a route is visited on nearly every session, or if several lazily loaded chunks immediately pull in the same large dependencies, we may end up trading one bottleneck for another.
Network round‑trips, repeated loading states, and fragmented navigation all matter. Performance isn't only about bundle size, it is also about how coherent the journey feels to the user.
Keep the Loading Experience Intentional
Users don't care that a chunk boundary exists, they care whether the interface responds clearly. That means loading indicators, route transitions, and error handling need as much attention as the split itself. A technically lazy‑loaded feature that produces a confusing blank pause isn't an especially good optimisation.
Angular gives us enough router events and component structure to manage this cleanly. The discipline is in keeping loading behaviour consistent across the application rather than improvising it per feature.
Preloading Can Help the Right Routes Feel Instant
Lazy loading and preloading are not opposites. A route can stay out of the initial bundle and still be fetched slightly later if the application has good reason to expect the user will need it soon.
That is where product judgement matters again. The best candidates for preloading are usually the next likely routes in a real journey, not every route in the application. Otherwise we risk undoing the very bundle savings lazy loading was supposed to create.
Making the Performance Gain Stick
Lazy loading works best when route boundaries map cleanly to feature boundaries and the route tree makes it obvious which areas are deferred. It also helps when loading states and route‑level behaviour are explicit enough to cover with focused tests instead of brittle end‑to‑end guesswork.
The best lazy‑loading strategies are rarely the cleverest. They are the ones the whole team can still understand six months later.
The Angular documentation covers the API details behind these ideas particularly well:
Measure After the Split
Lazy loading is easy to over‑credit because the architectural intent sounds right. We move code out of the initial bundle and feel as if the problem has been solved. In reality, shared dependencies, eager imports, and heavy follow‑on routes can quietly eat back the gain. That is why bundle inspection and real route timing still matter after the split has been introduced.
If the first screen is faster but the next meaningful step feels clumsy, the user will still read the product as slow. Good lazy loading improves the journey, not just the initial graph.
The quickest way to lose that gain is to treat every heavy feature the same. Some routes should be prefetched, some should stay cold, and some should probably be redesigned before another split is added.
That is why performance work still needs a user‑journey lens. The split is only useful if the route that matters next feels ready when the user gets there.
Wrapping up
Lazy loading is most useful when it matches the way people actually move through the product. If the boundaries are meaningful, the performance gain is clearer, and the architecture usually stays healthier too.
Key Takeaways
- Lazy loading protects the initial bundle by deferring less‑critical features.
- Route‑level boundaries usually provide the clearest and most maintainable split points.
- Performance gains only count if the resulting loading experience stays coherent.
Angular lazy loading works best when we treat it as product architecture rather than as a bundler trick. Once the split points match the way the application is actually used, the performance story becomes much more convincing.
Related Articles

A Brief Look at JavaScript’s Temporal Dates and Times API. A Brief Look at JavaScript's

Optimising Vue.js Performance with Lazy Loading and Code Splitting. Optimising Vue.js Performance with Lazy Loading and Code Splitting

Dynamic Imports and Code Splitting in Next.js. Dynamic Imports and Code Splitting in Next.js

Introducing Seeded Randomisation into an SSR Gatsby Project. Introducing Seeded Randomisation into an SSR Gatsby Project

Monotonic Stack: Solving the 'Daily Temperatures' Problem. Monotonic Stack: Solving the 'Daily Temperatures' Problem

Converting Between Camel, Snake, and Kebab Case in JavaScript. Converting Between Camel, Snake, and Kebab Case in JavaScript

Event Bubbling vs. Capturing in JavaScript. Event Bubbling vs. Capturing in JavaScript
A Simple Popup Window Using jQuery. A Simple Popup Window Using jQuery
How to Check an Element Exists with and Without jQuery. How to Check an Element Exists with and Without jQuery
How to Improve Your Time to First Byte (TTFB). How to Improve Your Time to First Byte (TTFB)

Toggle a Boolean in JavaScript. Toggle a Boolean in JavaScript

Common Accessibility Pitfalls in Web Development. Common Accessibility Pitfalls in Web Development