Got lazy loading working in your Angular app? Nice. That alone can make a huge difference to your load time.
However, lazy loading only takes you so far, because once users start clicking around, you might notice the app doesn’t feel quite as fast as it should...
The good news is that there's a solution to fix this - and that’s where preloading comes in.
In this guide, we’ll go deeper into how Angular handles route loading behind the scenes, and how you can use preloading to make navigation feel faster.
I’ll walk through built-in strategies, show you how to build your own, and even explore how to predict where the user’s going next.
Sidenote: If you want to learn more and improve your Angular skills, then check out my complete Angular Developer course:
This is the only Angular course you need to learn Angular, build enterprise-level applications from scratch, and get hired as an Angular Developer. Completely revamped for 2025!
With that out of the way, let’s get into this guide.
Lazy loading is often the first performance upgrade you reach for in Angular. You split your app into smaller chunks, keep the initial bundle light, and only load routes when the user needs them. It works well, especially for large apps with lots of features behind login.
But here’s the thing: lazy loading only helps with that first impression.
This means that the moment someone clicks to a new route, (say from the home page to the dashboard), they’re stuck waiting for Angular to fetch, download, and bootstrap the module.
Depending on the connection, that delay can be small or painfully noticeable.
This is because lazy loading helps with the startup time, but now you’ve got these awkward pauses sprinkled throughout your app. It’s fast, then slow, then fast again.
The solution to this issue is preloading.
Instead of waiting for a user to trigger the navigation, you can start loading certain routes in the background before they even click. That way, when they do navigate, the route is already there. No spinner. No delay. Just smooth, instant transitions.
Handy right?
So how do we do this? Well the good news is there’s a few options.
Angular gives you a couple of built-in options for controlling what gets preloaded. These are called preloading strategies, and they decide which lazy-loaded routes Angular should fetch in the background after the app has booted and everything’s stable.
Right out of the box, you’ve got two choices, and each has their pros and cons.
If you don’t configure anything, Angular defaults to NoPreloading
. That means lazy-loaded routes are only fetched when the user navigates to them.
This keeps background activity to a minimum, which is great for small apps or routes that aren’t used much, but it also means that every new route brings a slight pause. This is because they are triggered when the user clicks, and only then does Angular start downloading and bootstrapping the module.
So less background activity but slower load times.
If your app is small and most routes are likely to be visited, PreloadAllModules
is a quick win. It smooths out navigation with minimal setup
This option goes to the other extreme. As soon as the app finishes loading, Angular starts preloading all lazy-loaded routes in the background, with no conditions, and no filters.
The goal is to make route transitions feel almost instant, since the modules are already there when the user clicks. But the tradeoff is that you might end up downloading a lot of code the user never actually needs. That’s not ideal for mobile or limited data plans.
So what’s the solution?
Basically for anything more complex, or where you want to be selective about what gets preloaded, you’ll want to build a custom preloading strategy. This way you can preload certain routes, or even delay preloading based on the user’s role, device type or connection speed.
So let's show you how to do this.
At its core, a custom strategy is just a class that tells Angular which routes to preload, and which ones to skip. You hook it into the Angular router, and it decides what gets loaded in the background after the app boots.
Let’s walk through a basic example first and make it so we only preload the routes that have a specific flag, like data: { preload: true }
.
Start off by creating a class that implements PreloadingStrategy
. It will then give you access to each route config and the function Angular uses to load the module.
Inside your app/strategies
folder (or wherever makes sense for your app), add:
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
export class SelectivePreloadingStrategy implements PreloadingStrategy {
preload(route: Route, load: () => Observable<any>): Observable<any> {
return route.data?.preload ? load() : of(null);
}
}
This checks each route to see if it has data.preload
set to true
. If it does, Angular preloads it. Otherwise, it skips it.
Then, in your AppRoutingModule
, provide the strategy when you set up the router:
import { RouterModule, Routes } from '@angular/router';
import { SelectivePreloadingStrategy } from './strategies/selective-preloading.strategy';
const routes: Routes = [
{
path: 'dashboard',
loadChildren: () => import('./dashboard/dashboard.module').then(m => m.DashboardModule),
data: { preload: true }
},
{
path: 'settings',
loadChildren: () => import('./settings/settings.module').then(m => m.SettingsModule)
// no preload flag here
}
];
@NgModule({
imports: [
RouterModule.forRoot(routes, { preloadingStrategy: SelectivePreloadingStrategy })
],
exports: [RouterModule],
providers: [SelectivePreloadingStrategy]
})
export class AppRoutingModule {}
Now Angular will only preload the routes you’ve explicitly marked. This means you're now in full control and allows you to tweak it.
Once you’ve got a custom strategy in place, you’re not limited to just checking for preload: true
. You can use any logic you want to decide what should preload and when, and that gives you a lot of flexibility.
Let’s look at a few real-world scenarios.
If different users see different routes, there’s no point preloading everything for everyone right?
And so with this method, you can check the user’s role and conditionally preload just the relevant modules.
constructor(private authService: AuthService) {}
preload(route: Route, load: () => Observable<any>): Observable<any> {
const isAdminRoute = route.path?.startsWith('admin');
const isAdmin = this.authService.currentUserRole === 'admin';
return isAdminRoute && isAdmin ? load() : of(null);
}
In this example, only admin routes get preloaded, and only for admin users. (Because no one else would need this loaded up).
You can even check the user’s connection speed and skip preloading entirely on slow networks.
For example
The Network Information API makes this possible (with limited browser support).
const connection = (navigator as any).connection;
const isFastConnection = connection?.effectiveType === '4g';
preload(route: Route, load: () => Observable<any>): Observable<any> {
return isFastConnection ? load() : of(null);
}
Here we’re checking to see if the user is using 4g cellular connection, and if so, to skip potential preloading lag.
Sometimes, you don’t want to preload immediately.
For example
Maybe your app is fetching user data or other resources during boot. If that’s the case, you can choose to stagger preloading to give those tasks a head start.
preload(route: Route, load: () => Observable<any>): Observable<any> {
return timer(3000).pipe(
switchMap(() => route.data?.preload ? load() : of(null))
);
}
Here you can see we’ve set up a 3 second wait before starting any preload for routes that are flagged.
If you can guess where the user is likely to go next, you can start loading that route in the background before they even click. This is known as predictive preloading, and it’s one of the most effective ways to make your app feel fast and seamless.
For example
Let’s say your app takes users from a login page straight to a dashboard. Once they’re logged in, it’s safe to assume that dashboard is coming up next. You could start preloading it while the user is still finishing authentication.
Or maybe your layout includes links to Profile, Settings, and Notifications. If someone is on their Profile page, there’s a good chance one of those other two is next.
If you already know the navigation flow, you can preload those routes early and remove that delay altogether.
Handy right?
This is actually one of the most common use cases, so let’s break this down into more detail and show you how to use it.
There are a few ways to handle predictive preloading in Angular. Some you plan yourself. Others just work automatically based on what the user sees.
Let’s break down 4 common approaches.
You can use custom route data to describe what should load after another route.
For example
Here’s how you might define it:
{
path: 'profile',
loadChildren: () => import('./profile/profile.module').then(m => m.ProfileModule),
data: { preloadAfter: 'dashboard' }
}
Then in your custom strategy, you check the current route and compare it:
constructor(private router: Router) {}
preload(route: Route, load: () => Observable<any>): Observable<any> {
const currentRoute = this.router.url.slice(1); // 'dashboard', etc.
const preloadAfter = route.data?.preloadAfter;
return preloadAfter === currentRoute ? load() : of(null);
}
This lets you preload routes in sequence based on your app’s expected navigation flow.
Sometimes you want full control. Maybe after login, you know the dashboard is next, so you preload it directly from the component or service:
import('./dashboard/dashboard.module').then(m => m.DashboardModule);
This loads the module quietly in the background, so when the user eventually navigates to it, the route is ready to go instantly.
Not everything has to be predicted manually. Sometimes the best indicator is what’s already on screen, such as a link in the navbar, a tab, or a sidebar menu.
That’s exactly what Quicklink does.
It watches the screen, finds visible links, and preloads them automatically in the background. Once it finds them, it quietly preloads those URLs in the background using browser idle time.
If your UI shows links to other routes, Quicklink starts fetching those routes as soon as they come into view.
There are a couple of ways to integrate Quicklink, but the easiest is with the @ngx-quicklink
package that’s built specifically for Angular apps.
To get started:
npm install @ngx-quicklink
Then update your routing module like this:
import { QuicklinkModule, QuicklinkStrategy } from '@ngx-quicklink';
@NgModule({
imports: [
RouterModule.forRoot(routes, {
preloadingStrategy: QuicklinkStrategy
}),
QuicklinkModule
],
exports: [RouterModule]
})
export class AppRoutingModule {}
Quicklink will now automatically preload any lazy-loaded route that’s linked from an <a routerLink="">
on the screen. You don’t need to manually mark routes or build custom logic, it just works by observing what’s visible.
It’s not a good fit for routes that don’t have visible links, or where navigation is triggered by buttons, modals, or dynamic behavior. But for classic layouts with visible navigation, it can make your app feel a lot faster with almost no extra work.
Angular is not the only library developed by Google to help you build web applications. Guess.js is a set of tools developed by Google that uses Google Analytics data to analyze navigation patterns. It builds a predictive model during your build process and injects smart preloading into your Angular app.
Basically, it preloads routes your users are likely to visit next, based on real usage data.
How cool is that?
Guess.js strikes the perfect balance by using actual user behavior to preload only what's likely to be used next.
**How it works))
@guess/webpack
plugin analyzes that data during buildAngular preloads routes based on those hints at runtime
You can install the webpack plugin with the following command:
npm install @guess/webpack
Next, with the help of the ngx-build-plus module, you can update the builder with the following configuration:
const { GuessPlugin } = require('@guess/webpack');
plugins: [
new GuessPlugin({
GA: 'UA-XXXXXX-X', // your Google Analytics ID
routeProvider() {
return require('./dist/routes.json');
}
})
]
So, now you know the 4 most common ways to implement predictive preloading, let's walk through some best practices and common mistakes.
This is the most common mistake, because it’s tempting to preload every lazy-loaded route to make navigation feel instant. However, doing that defeats the purpose of lazy loading entirely, because you’re loading code that the user might never need.
Sure, on fast connections it might not matter, but on mobile or slow networks, it can slow everything down, including your initial load.
So make sure to only preload what’s likely to be used soon. Use conditions, predictions, or visibility (like with Quicklink), but always preload with intent.
Some routes just aren’t urgent. Admin panels, rarely used settings screens, and feature toggles can all wait until later in the session, or until the user actually clicks.
If it’s not likely to be visited soon, skip it. You can always load it reactively when it’s actually needed.
Preloading is all about background loading, but if you’re not careful with how your routes and modules are split, you can accidentally pull large chunks into your main bundle.
Tools like source-map-explorer
or webpack-bundle-analyzer
can help you see what’s going where. You want to keep your main bundle small, even while using preloading to load more on the side.
You’re not stuck with just one approach. You can use a custom preloading strategy for fine-grained control, Quicklink for visible links, and predictive logic for known patterns, all in the same app.
Just make sure your logic stays maintainable. Preloading is supposed to speed things up, not introduce bugs or complexity.
So as you can see, preloading gives you the control to make route transitions feel fast and seamless - all without bloating your initial load. Now it's your turn. Try adding preloading to your app and see the difference it makes. Start with the basics, then layer in smarter techniques as your app grows.
And if you want to take it further, keep an eye out for our upcoming guide on full Angular performance optimization.
Remember, if you struggle with any of the concepts in this guide, or want to take a deep dive into Angular, check out my complete Angular Developer course:
This is the only Angular course you need to learn Angular, build enterprise-level applications from scratch, and get hired as an Angular Developer.
Want a sneak peak?
I went ahead and uploaded the first 4 hours of the course below for free:
Plus, once you join, you'll have the opportunity to ask questions in our private Discord community from me, other students and working professionals.
Not only that, but as a member - you’ll also get access to every course in the Zero To Mastery library!
Check out some of my other Angular articles, guides and cheatsheets!