Hook — 06 : 02 a.m., Playa Venao (Panama) ↔ Guadalajara stand-up
I was debugging in flip-flops when Carla in Mexico shared her screen: our Angular storefront took 8 MB of JavaScript before the first product image appeared. Every route imported the whole kitchen sink. While the Pacific rolled behind me, we refactored two monster NgModules into lazy-loaded feature modules, trimmed the initial bundle to 950 kB, and First Contentful Paint dropped from 4.6 s to 1.7 s on a modest Moto G. That surf-adjacent rescue is our roadmap today—by the end you’ll wire up lazy routes, prefetch like a pro, and sidestep common pitfalls, all in Angular 18.
Why Lazy Loading Still Matters
Pain Point | User Impact | Lazy Module Fix |
---|---|---|
“White screen” on first visit | High bounce rate, bad Core Web Vitals | Ship code only for the visited route |
Growing teams add features weekly | Initial bundle balloons | Each feature becomes its own chunk |
Autoplay video or heavy charts on dashboard | Mobile data throttled | Route guard loads feature only after login |
Server-side rendering hydration delay | CLS & FID regressions | Incremental hydration hydrates per-route |
Concept in Plain English
Lazy loading splits your Angular app into bite-sized feature modules. The router downloads a module’s JavaScript the first time its route is activated, instead of during the initial bundle. Think “carry-on luggage” instead of “checked bags” for every trip.
Step-by-Step Walkthrough
1 — Generate a Feature Module
bashCopyEditng g module products --route=products --standalone --module=app
Flags explained:
--route=products
adds a new route path/products
.--standalone
(Angular 18 default) means no NgModule boilerplate.--module=app
updates the root router config automatically.
Angular CLI inserts:
tsCopyEditexport const routes: Routes = [
{
path: 'products',
loadChildren: () =>
import('./products/products.routes').then(m => m.routes),
},
];
Tip — keep feature code under
src/app/features/
to prevent barrel-file spaghetti.
2 Build the Feature in Isolation
Inside products/
add a routed component:
bashCopyEditng g component products/pages/product-list --standalone
products.routes.ts
:
tsCopyEditimport { Route } from '@angular/router';
import { ProductListComponent } from './pages/product-list/product-list.component';
export const routes: Route[] = [
{ path: '', component: ProductListComponent },
];
Now ng serve
and hit /products
—Network tab shows a new products-chunk.js
after navigation.
3 Preloading Strategies
Angular‘s built-in Quicklink-style preloading fetches idle-time routes.
tsCopyEditbootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(RouterModule.forRoot(routes, {
preloadingStrategy: PreloadAllModules, // or QuicklinkStrategy from ngx-quicklink
})),
],
});
Users on 3 G? Create a custom strategy that checks navigator.connection.effectiveType
.
4 Route Guards for Auth-Gated Features
tsCopyEditexport const routes: Route[] = [
{
path: 'admin',
loadChildren: () => import('./admin/admin.routes').then(m => m.routes),
canMatch: [AuthGuard], // loads file only if guard passes
},
];
AuthGuard
returns true
or a redirect UrlTree
; Angular skips the network request when false—perfect for paywalled dashboards.
5 Shared Modules vs. Duplication
Create a slim SharedModule
with common components — buttons, pipes — and import it into every feature. Because it’s in the initial bundle, you avoid duplicated code across lazy chunks.
bashCopyEditng g module shared --standalone
In a feature:
tsCopyEditimport { SharedModule } from '../shared/shared.module';
export const routes: Route[] = [
{ path: '', component: ProductListComponent, providers: [provideHttpClient()] },
];
Call-Out Table — CLI Fast Track
CLI Command | One-Liner Purpose |
---|---|
ng g module xxx --route=y --standalone --module=app | Generate lazy feature |
ng g guard auth --standalone --implements=CanMatch | Auth guard for lazy route |
ng build --stats-json | Inspect chunk sizes |
ng deploy | Verify lazy chunks on CDN |
Remote-Work Insight 🌎
During a sprint in Costa Rica’s Osa Peninsula, our hot-reload took 25 s on satellite Wi-Fi. We flipped
esbuildOptions: { splitting: true }
inangular.json
and enabled incremental builds. Dev-server restarts fell below 5 s—enough to push fixes before the daily power cut.
Common Pitfalls & Fixes
Symptom | Root Cause | Patch |
---|---|---|
“Cannot find module” runtime error | Wrong path in import() | Use relative './feature.routes' |
Two lazy modules share same component | Code duplicated in both chunks | Move component to SharedModule |
Googlebot can’t crawl deep link | SSR not enabled | ng add @angular/ssr then deploy |
Scroll position reset on nav | Default router behavior | RouterModule.forRoot(routes, {scrollPositionRestoration:'enabled'}) |
Performance & Accessibility Checkpoints
- Lighthouse → Largest Contentful Paint should drop once hero images load without dashboard JS.
- Chrome Coverage Tab — unused JS bytes at first load should be < 40 %.
- Core Web Vitals overlay — Interaction to Next Paint remains ≤ 200 ms after lazy nav.
- Focus Management — ensure
<h1>
exists in each lazy page so screen readers announce.
Hands-On: Skeleton Loader with Route Data
Add resolver delay simulation:
tsCopyEditexport const routes: Route[] = [
{
path: '',
loadComponent: () => import('./profile.component').then(m => m.ProfileComponent),
data: { preload: true, skeleton: 'profile' },
},
];
Create skeleton directive that reads ActivatedRoute.data.skeleton
and shows a shimmering placeholder until component hydrates—a11y-friendly and visually smooth.
SVG Diagram Idea
- Initial bundle =
main.js + shared.js
. - User navigates
/products
➜ Router triggers dynamicimport()
➜products-chunk.js
network request. - Preloading strategy fetches
/cart
chunk during idle. - AuthGuard denies
/admin
, no chunk fetched.
Wrap-Up
Lazy-loaded feature modules let your Angular app feel native on the patchy cellular networks I hit traveling from Panama City to rural Brazil. Generate modules with the CLI, preload wisely, guard private routes, and audit chunk sizes regularly. Do that, and users—and your remote teammates—will see pages, not progress bars.
Got a lazy-loading win or horror story? Drop it below; I’ll reply between airport layovers and late-night arepa sessions.