| name | lazy-loading |
| description | Wire a route so its feature is genuinely lazy-loaded. Use when adding a route with loadChildren or loadComponent, when deciding what a library barrel may export, or when a lazy chunk turns out to be bundled eagerly. |
Skill: Lazy Loading Rules
Lazy loading is a critical performance pattern in this project. Violating these rules makes lazy loading ineffective — the module gets bundled eagerly, defeating the purpose entirely.
The Core Rule
A feature library is lazy-loaded only if nothing from it is imported statically outside its own boundary.
The moment you add a direct import of a component or service from a feature lib into the app bundle (outside a dynamic import()), the entire lib gets included in the initial bundle.
What the index.ts must export
Each lazy-loaded feature lib's index.ts exports only the routes:
export * from './lib/pages/user.routes';
Never do this if the lib is lazy-loaded:
export * from './lib/pages/profile/profile';
export * from './lib/services/profile/profile.service';
If the app imports the Profile component statically at the top of a file, Angular's bundler sees the dependency and includes it in the initial chunk.
libs/profile/src/index.ts also exports ProfileStore. That is safe: the store is
only ever referenced from inside the lazy chunk (the route's providers), so
exporting it does not pull anything into the initial bundle. Exporting it keeps the
store reachable from tests without deep-importing library internals.
How to load a feature in the app
Use loadChildren with a dynamic import():
{
path: 'profile',
loadChildren: () =>
import('@libs/profile').then((m) => m.profileRoutes),
}
Never use component or loadComponent pointing to a lib page component:
import { Profile } from '@libs/profile';
{
path: 'profile',
component: Profile,
}
Internal imports are fine
Inside the feature lib, components can import each other freely. These imports stay within the lazy chunk:
import { UserInfoComponent } from '../../molecules/user-info/user-info';
import { ProfileStore } from '../../+state/profile.store';
The rule only applies to imports from outside the lib boundary (i.e., from the app or other libs).
Shared libs are always eager
libs/shared/* (entity, ui, translation) and libs/environment are shared across the app and are part of the main bundle. They do not need lazy loading. Importing from them anywhere is fine:
import { User } from '@libs/entity';
import { HeaderComponent } from '@libs/ui';
How to verify lazy loading is working
After building the app:
npx nx build demo-app --stats-json
Inspect the output chunks. The feature lib should appear as a separate chunk file (e.g., libs_profile_src_index_ts.js), not merged into main.js.
Summary table
| Scenario | Correct? |
|---|
loadChildren with dynamic import('@libs/profile') | Yes |
index.ts exports only routes | Yes |
| Internal lib imports between components/state/service | Yes |
component: Profile in app routes with static import | No |
index.ts exports a component or service | No (unless the lib is intentionally eager) |
App file has import { Profile } from '@libs/profile' | No |