| |

Angular 96 ๐Ÿ…ฐ๏ธ Performance โ€” Bundles and Loading

โฑ๏ธ Estimated read: 15 min | |

The previous chapter covered change detection and rendering โ€” the work Angular does while the application is running. This chapter covers the work that happens before the application runs: downloading, parsing, and executing the JavaScript, CSS, and assets that make up the application. A fast application is not just one that renders efficiently. It is one that starts quickly. The bundle size and loading strategy determine how long the user waits before the first meaningful content appears.

The Angular CLI uses esbuild as its build engine. The default build produces a set of bundles: the main bundle, the polyfills bundle, the styles bundle, and a set of lazy-loaded chunks. The size of these bundles determines the download time. The loading strategy determines when each bundle is fetched. The two together determine the user’s perceived performance.

Key point: Angular’s build is optimized by default, but the defaults are not enough for every application. The initial bundle includes the application’s root code and everything imported by it. A large initial bundle means a slow first paint. The techniques in this chapter โ€” lazy loading, @defer, tree-shaking, bundle budgets, and preloading โ€” reduce the initial bundle and defer the rest until it is needed.


Why bundle size matters

A JavaScript bundle is not free. The browser must download it, parse it, compile it, and execute it. Each step takes time. On a fast desktop with a fast connection, the difference between a 200 KB bundle and a 2 MB bundle is measured in milliseconds. On a mid-range phone with a slow connection, the difference is measured in seconds. The user sees a blank screen until the bundle is loaded and the application is bootstrapped.

The download problem. A 1 MB bundle over a 3G connection takes roughly 8 seconds to download. Over a 4G connection, roughly 2 seconds. Over a fast Wi-Fi connection, roughly 200 milliseconds. The network is the bottleneck for most users. Reducing the bundle size is the most direct way to reduce the initial load time.

The parse problem. The browser must parse the JavaScript before it can execute it. Parsing is fast for small bundles and slow for large ones. The V8 engine can parse roughly 1 MB of JavaScript per second. A 3 MB bundle takes roughly 3 seconds to parse, even after it is downloaded.

The execution problem. The top-level code in the bundle runs during bootstrap. A large Angular application with many components registered at startup executes more code before the first render. The @defer block and lazy loading reduce the amount of code that must execute before the first paint.

The cache problem. A bundle that changes on every deployment is re-downloaded on every visit. A bundle that is cached by the browser is loaded from disk. The Angular CLI uses content hashing: the filename includes a hash of the contents. When the contents change, the hash changes, and the browser downloads the new version. When the contents are unchanged, the hash is the same, and the browser uses the cached version.

The trade-off. Smaller bundles mean more chunks and more requests. A single 1 MB bundle is faster to download than ten 100 KB bundles over HTTP/1.1 because of the per-request overhead. But HTTP/2 and HTTP/3 multiplex the requests, and the per-request overhead is minimal. The modern approach is to split the application into many small chunks and let the bundler and the browser handle the loading. The initial chunk should be as small as possible.


a. The Default Bundle Structure

When you run ng build, the Angular CLI produces a set of files in the dist/ directory. The main files are:

main.js is the application bundle. It contains the root component, the bootstrapping code, and every module imported by the root component. It is the largest bundle in a typical application.

polyfills.js contains the polyfills for the JavaScript features that the application uses but the target browsers do not support. Angular no longer includes Zone.js by default in zoneless applications. In traditional applications, Zone.js is in the polyfills bundle.

styles.css contains the global styles. Component styles are included in the JavaScript bundles and injected at runtime.

vendor.js contains the third-party libraries. In the modern esbuild-based build, the vendor code is merged into the main bundle by default. The vendorChunk option splits it out.

Lazy chunks are created for routes and components loaded with loadChildren or loadComponent. Each lazy chunk is a separate file that is downloaded only when the route is activated.

The build output includes a summary of the sizes. The Initial total is the size of the bundles that are loaded on the first visit. The Lazy chunks are the sizes of the bundles that are loaded on demand.

Initial total | 450.23 kB
Lazy          |  80.12 kB

The initial total is the number that matters most. It is the size of the code the user must download before the application is usable.


b. Lazy Loading Routes

The most impactful optimization for a large application is lazy loading. A route that is lazy-loaded is not included in the initial bundle. It is downloaded only when the user navigates to the route.

The lazy loading is configured with loadComponent or loadChildren:

export const routes: Routes = [
  {
    path: '',
    loadComponent: () => import('./home/home.component').then(m => m.HomeComponent),
  },
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent),
  },
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES),
  },
];

The import() function returns a promise that resolves to the module. The bundler sees the dynamic import and creates a separate chunk for the module. The chunk is downloaded when the route is activated.

The effect on the initial bundle is significant. If the application has a home page and a dashboard, and the dashboard is lazy-loaded, the dashboard’s code is not in the initial bundle. The user downloads the home page first. When they navigate to the dashboard, the dashboard chunk is downloaded.

The lazy loading can be combined with preloading. The preloading strategy loads the lazy chunks in the background after the initial application is loaded. The user does not wait for the chunk when they navigate to the route because the chunk is already in the browser’s cache.

import { provideRouter, withPreloading, PreloadAllModules } from '@angular/router';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, withPreloading(PreloadAllModules)),
  ],
};

The PreloadAllModules strategy preloads all lazy routes. For a custom strategy, the PreloadingStrategy interface can be implemented to preload only specific routes .


c. Deferring Components with @defer

The @defer block is the component-level equivalent of lazy loading. It defers the loading of a component and its dependencies until a trigger fires.

@defer (on viewport) {
  <app-heavy-chart [data]="chartData()" />
} @placeholder {
  <div class="chart-placeholder">Chart loads when visible</div>
} @loading (minimum 500ms) {
  <div class="spinner">Loading chart...</div>
} @error {
  <div class="error">Failed to load chart.</div>
}

The @defer block is compiled into a dynamic import. The component and its dependencies are placed in a separate chunk. The chunk is downloaded when the trigger fires. The @placeholder is shown before the trigger fires. The @loading is shown while the chunk is downloading. The @error is shown if the download fails.

The triggers are idle, viewport, interaction, hover, immediate, timer, and when. The viewport trigger uses the Intersection Observer API to detect when the placeholder enters the viewport. The interaction trigger detects a click or key press. The hover trigger detects a mouse over. The timer trigger fires after a specified duration .

The @defer block is the most precise tool for reducing the initial bundle. A component that is below the fold, behind a tab, or behind an interaction can be deferred. Its code is not part of the initial bundle. It is downloaded only when the user needs it.


Complete Example Session

This session demonstrates the bundle optimization techniques on a small Angular application.

// ============================================
// PART 1: THE ROUTES WITHOUT LAZY LOADING
// ============================================

// Before optimization:
import { Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { AdminComponent } from './admin/admin.component';
import { SettingsComponent } from './settings/settings.component';

export const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'dashboard', component: DashboardComponent },
  { path: 'admin', component: AdminComponent },
  { path: 'settings', component: SettingsComponent },
];

// All components are imported statically.
// All components are in the initial bundle.
// The initial bundle is large.

// ============================================
// PART 2: THE ROUTES WITH LAZY LOADING
// ============================================

// After optimization:
import { Routes } from '@angular/router';

export const routes: Routes = [
  {
    path: '',
    loadComponent: () => import('./home/home.component').then(m => m.HomeComponent),
  },
  {
    path: 'dashboard',
    loadComponent: () => import('./dashboard/dashboard.component').then(m => m.DashboardComponent),
  },
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.routes').then(m => m.ADMIN_ROUTES),
  },
  {
    path: 'settings',
    loadComponent: () => import('./settings/settings.component').then(m => m.SettingsComponent),
  },
];

// The components are loaded dynamically.
// Each component is in a separate chunk.
// The initial bundle contains only the shell.

// ============================================
// PART 3: THE BUILD OUTPUT
// ============================================

// ng build --configuration production

// Before lazy loading:
// Initial total | 1.45 MB
// Lazy          |  0 B

// After lazy loading:
// Initial total | 380 kB
// Lazy          | 420 kB
//   - dashboard | 120 kB
//   - admin     | 180 kB
//   - settings  | 120 kB

// The initial bundle is 73% smaller.
// The lazy chunks are loaded on demand.

// ============================================
// PART 4: THE PRELOADING STRATEGY
// ============================================

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideRouter, withPreloading, PreloadAllModules } from '@angular/router';
import { routes } from './app.routes';

export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, withPreloading(PreloadAllModules)),
  ],
};

// The preloading strategy loads the lazy chunks
// in the background after the initial load.
// The user does not wait when navigating.

// ============================================
// PART 5: THE DEFER BLOCK
// ============================================

// dashboard.component.html
@Component({
  selector: 'app-dashboard',
  template: `
    <h1>Dashboard</h1>
    <app-summary [data]="summary()" />

    @defer (on viewport) {
      <app-heavy-chart [data]="chartData()" />
    } @placeholder {
      <div class="chart-placeholder">Chart loads when visible</div>
    } @loading (minimum 500ms) {
      <div class="spinner">Loading chart...</div>
    }
  `,
})
export class DashboardComponent {
  summary = signal<Summary | null>(null);
  chartData = signal<ChartData | null>(null);
}

// The heavy chart is in a separate chunk.
// The chunk is downloaded when the chart enters the viewport.

// ============================================
// PART 6: THE TREE-SHAKING
// ============================================

// Tree-shaking removes unused code from the bundle.
// The build is tree-shaken by default.

// โŒ This import includes the entire library:
import * as _ from 'lodash';

// โœ… This import includes only the function:
import { debounce } from 'lodash-es';

// Use named imports instead of namespace imports.
// Use the ESM build of the library.

// ============================================
// PART 7: THE BUNDLE BUDGETS
// ============================================

// angular.json
{
  "budgets": [
    {
      "type": "initial",
      "maximumWarning": "500kB",
      "maximumError": "1MB"
    },
    {
      "type": "anyComponentStyle",
      "maximumWarning": "4kB",
      "maximumError": "8kB"
    },
    {
      "type": "any",
      "maximumWarning": "100kB",
      "maximumError": "200kB"
    }
  ]
}

// The budgets set limits on the bundle sizes.
// The build fails if a bundle exceeds the maximumError.
// The build warns if a bundle exceeds the maximumWarning.

// ============================================
// PART 8: THE BUILD ANALYZER
// ============================================

// Install the analyzer:
// npm install --save-dev esbuild-visualizer

// Build with the stats file:
// ng build --stats-json

// Analyze the bundle:
// npx esbuild-visualizer --metadata dist/app/stats.json

// The analyzer shows:
// - Which modules are in the bundle
// - The size of each module
// - The size of each chunk
// - The dependencies between chunks

// ============================================
// PART 9: THE OPTIMIZATION CHECKLIST
// ============================================

// 1. Lazy load all routes that are not on the initial page
// 2. Use @defer for components below the fold
// 3. Use preloading for routes the user is likely to visit
// 4. Use named imports instead of namespace imports
// 5. Use the ESM build of libraries
// 6. Set bundle budgets to prevent regressions
// 7. Analyze the bundle to find large modules
// 8. Remove unused dependencies
// 9. Use the production build for deployment
// 10. Monitor the bundle size in CI

// ============================================
// PART 10: THE RESULTS
// ============================================

// Before optimization:
// Initial total | 1.45 MB
// Time to interactive (4G) | 3.2s

// After optimization:
// Initial total | 380 kB
// Time to interactive (4G) | 1.1s

// The initial bundle is 73% smaller.
// The time to interactive is 66% faster.
// The lazy chunks are loaded on demand.
// The heavy components are deferred.
// The bundle budgets prevent regressions.

The ten parts cover the routes without lazy loading, the routes with lazy loading, the build output, the preloading strategy, the @defer block, the tree-shaking, the bundle budgets, the build analyzer, the optimization checklist, and the results.


Quick Reference

The Bundle Files

FileContents
main.jsApplication code
polyfills.jsPolyfills (Zone.js in traditional apps)
styles.cssGlobal styles
vendor.jsThird-party libraries (if split)
Lazy chunksRoute and component chunks

The Lazy Loading Syntax

SyntaxPurpose
loadComponent: () => import(...)Lazy load a component
loadChildren: () => import(...)Lazy load routes
withPreloading(PreloadAllModules)Preload all lazy chunks
Custom PreloadingStrategySelective preloading

The @defer Triggers

TriggerFires When
idleBrowser is idle
viewportElement enters viewport
interactionClick or key press
hoverMouse hovers
immediateAfter non-deferred content
timerAfter specified duration
whenCustom condition is true

The Bundle Budgets

TypePurpose
initialInitial bundle size
anyComponentStyleComponent style size
anyAny bundle size
bundleSpecific bundle size

The Optimization Techniques

TechniqueImpact
Lazy loadingLarge
@deferLarge
Tree-shakingMedium
PreloadingPerceived speed
Bundle budgetsPrevent regression

Best Practices

โœ… Do This:

// Lazy load all routes
loadComponent: () => import('./dashboard.component').then(m => m.DashboardComponent) // โœ…
<!-- Defer below-the-fold components -->
@defer (on viewport) { <heavy-chart /> }                         <!-- โœ… -->
// Use named imports
import { debounce } from 'lodash-es';                            // โœ…
// Set bundle budgets
{ "type": "initial", "maximumWarning": "500kB" }                 // โœ…
// Preload lazy chunks
provideRouter(routes, withPreloading(PreloadAllModules))         // โœ…

โŒ Don’t Do This:

// Don't statically import all components
import { DashboardComponent } from './dashboard.component';      // โŒ
// Don't use namespace imports
import * as _ from 'lodash';                                     // โŒ
<!-- Don't load everything eagerly -->
<app-heavy-chart />  <!-- in the initial template              --> <!-- โŒ -->
// Don't ignore bundle budgets
"budgets": []  // no limits                                      // โŒ

Common Pitfalls

PitfallWhy It HappensFix
Initial bundle too largeNo lazy loadingLazy load routes
Bundle size growingNo budgetsSet budgets in angular.json
Unused code in bundleNamespace importsUse named imports
Deferred component not loadingWrong triggerCheck the @defer trigger
Lazy route not loadingWrong import pathCheck the path
Slow first paintLarge polyfillsAudit polyfills

Real-World Examples

1. Lazy Load a Route

loadComponent: () => import('./dashboard.component').then(m => m.DashboardComponent)

2. Lazy Load Children

loadChildren: () => import('./admin.routes').then(m => m.ADMIN_ROUTES)

3. Preload All Modules

provideRouter(routes, withPreloading(PreloadAllModules))

4. Defer on Viewport

@defer (on viewport) { <heavy-chart /> }

5. Defer on Interaction

@defer (on interaction) { <comments /> }

6. Named Import

import { debounce } from 'lodash-es';

7. Bundle Budget

{ "type": "initial", "maximumWarning": "500kB" }

8. Build with Stats

ng build --stats-json

9. Analyze Bundle

npx esbuild-visualizer --metadata dist/app/stats.json

10. Production Build

ng build --configuration production

Visual

The Bundle Structure

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  INITIAL BUNDLE                              โ”‚
โ”‚    main.js       โ† application code          โ”‚
โ”‚    polyfills.js  โ† polyfills                 โ”‚
โ”‚    styles.css    โ† global styles             โ”‚
โ”‚                                              โ”‚
โ”‚  LAZY CHUNKS                                 โ”‚
โ”‚    dashboard.js  โ† loaded on navigation      โ”‚
โ”‚    admin.js      โ† loaded on navigation      โ”‚
โ”‚    settings.js   โ† loaded on navigation      โ”‚
โ”‚                                              โ”‚
โ”‚  DEFERRED CHUNKS                             โ”‚
โ”‚    chart.js      โ† loaded on viewport        โ”‚
โ”‚    comments.js   โ† loaded on interaction     โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Lazy Loading Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  LAZY LOADING                                โ”‚
โ”‚                                              โ”‚
โ”‚  Initial load:                               โ”‚
โ”‚    main.js + polyfills.js + styles.css       โ”‚
โ”‚    โ””โ”€ Application shell renders              โ”‚
โ”‚                                              โ”‚
โ”‚  User navigates to /dashboard:               โ”‚
โ”‚    dashboard.js downloads                    โ”‚
โ”‚    โ””โ”€ Dashboard component renders            โ”‚
โ”‚                                              โ”‚
โ”‚  User navigates to /admin:                   โ”‚
โ”‚    admin.js downloads                        โ”‚
โ”‚    โ””โ”€ Admin component renders                โ”‚
โ”‚                                              โ”‚
โ”‚  The initial load is smaller.                โ”‚
โ”‚  The chunks are loaded on demand.            โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The @defer Lifecycle

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  @defer LIFECYCLE                            โ”‚
โ”‚                                              โ”‚
โ”‚  1. Initial render                           โ”‚
โ”‚     โ””โ”€ @placeholder shown                    โ”‚
โ”‚                                              โ”‚
โ”‚  2. Trigger fires                            โ”‚
โ”‚     โ””โ”€ Chunk download begins                 โ”‚
โ”‚                                              โ”‚
โ”‚  3. @loading shown                           โ”‚
โ”‚     โ””โ”€ While chunk downloads                 โ”‚
โ”‚                                              โ”‚
โ”‚  4. Chunk downloaded                         โ”‚
โ”‚     โ””โ”€ Component renders                     โ”‚
โ”‚                                              โ”‚
โ”‚  5. Error (if download fails)                โ”‚
โ”‚     โ””โ”€ @error shown                          โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The Bundle Budget

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  BUNDLE BUDGETS                              โ”‚
โ”‚                                              โ”‚
โ”‚  initial:                                    โ”‚
โ”‚    maximumWarning: 500kB                     โ”‚
โ”‚    maximumError: 1MB                         โ”‚
โ”‚                                              โ”‚
โ”‚  anyComponentStyle:                          โ”‚
โ”‚    maximumWarning: 4kB                       โ”‚
โ”‚    maximumError: 8kB                         โ”‚
โ”‚                                              โ”‚
โ”‚  any:                                        โ”‚
โ”‚    maximumWarning: 100kB                     โ”‚
โ”‚    maximumError: 200kB                       โ”‚
โ”‚                                              โ”‚
โ”‚  The build fails if a budget is exceeded.    โ”‚
โ”‚  The build warns if a warning is exceeded.   โ”‚
โ”‚                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
Initial bundlemain.js, polyfills.js, styles.css
Lazy chunksLoaded on navigation
Deferred chunksLoaded on trigger
Lazy loadingloadComponent, loadChildren
PreloadingwithPreloading(PreloadAllModules)
Defer triggersidle, viewport, interaction, hover, timer, when
Tree-shakingNamed imports, ESM builds
Bundle budgetsinitial, anyComponentStyle, any
Build analyzeresbuild-visualizer
Production buildng build --configuration production

Key takeaways:

  • The initial bundle is the code the user must download before the application is usable. Reducing its size is the most direct way to reduce the initial load time. The main.js, polyfills.js, and styles.css files make up the initial bundle .
  • Lazy loading routes is the most impactful optimization. A route loaded with loadComponent or loadChildren is not in the initial bundle. It is downloaded only when the route is activated. The initial bundle can shrink by 70% or more .
  • @defer defers components and their dependencies. The component is placed in a separate chunk and downloaded only when a trigger fires. The viewport trigger is the most common for below-the-fold content. The interaction trigger is used for user-initiated content .
  • Preloading loads lazy chunks in the background. The PreloadAllModules strategy preloads all lazy routes after the initial load. The user does not wait when navigating because the chunk is already cached. A custom PreloadingStrategy can preload only specific routes .
  • Tree-shaking removes unused code. The build is tree-shaken by default, but the tree-shaking is only effective if the imports are structured correctly. Named imports are tree-shakeable. Namespace imports are not .
  • Bundle budgets prevent regressions. The angular.json file can set size limits for the initial bundle, the component styles, and any bundle. The build fails if a budget is exceeded. The budgets are a guard against accidental bundle growth .
  • The build analyzer shows what is in the bundle. The --stats-json flag produces a stats file. The esbuild-visualizer tool renders the stats as a treemap. The analyzer identifies the largest modules and the opportunities for optimization.

Remember: Performance is not just about what happens after the application loads. It is about how long the user waits before the application loads. The bundle size and the loading strategy determine that wait. Lazy loading reduces the initial bundle. @defer defers what is not needed. Preloading hides the latency. Tree-shaking removes the unused. Bundle budgets prevent the regression. The build analyzer shows what is in the bundle. The production build optimizes what remains.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!