Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Lazy loading can reduce the JavaScript and media a web application needs at startup by postponing non-critical resources until a user navigates to them, a component first renders, or media approaches the viewport. It helps when that saved initial work matters more than the extra wait and requests later. Keep the application shell and primary landing content eager, and measure the result rather than assuming lazy loading is always faster.

What lazy loading changes

Lazy loading treats selected resources as non-blocking until they are needed. Instead of fetching and processing every route, component, or image at startup, the application delays some of that work until a later trigger. This is one form of code splitting: an application can separate code into smaller chunks and request a chunk when the relevant entry point or dynamic import is reached.

The benefit is a smaller initial workload: fewer bytes to download and, for deferred JavaScript, less code to parse and execute before the user can interact. The cost is work moved to a later moment. That may mean an additional request, a visible loading state, or a delay when the user opens a route or feature. Whether the trade-off is worthwhile depends on which resources are deferred and when users need them.

Lazy-load JavaScript on demand

Use the JavaScript import() expression when a module is only needed after a specific action or condition. It returns a promise, so the application can wait for the module before calling its exported functionality:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
button.addEventListener('click', async () => {
  const { openEditor } = await import('./editor.js');
  openEditor();
});

In this example, the editor module is requested after the button is clicked rather than as part of the initial static imports. Keep static imports for dependencies needed immediately at startup; deferring those can make the initial experience wait for code it needs to render.

Dynamic imports also give build tools a point at which to split application code into separate chunks. Entry-point splitting separates application entry points, while dynamic splitting follows import() expressions. Splitting a module does not make its functionality disappear; it changes when the relevant code is fetched and used.

Defer off-screen images and frames

For non-critical images and embedded frames that are initially off-screen, the browser’s native lazy-loading hint is often the simplest option:

<img src="photo.jpg" width="800" height="600" loading="eager" alt="Description">
<iframe src="video-player.html" loading="eager" title="Video player"></iframe>

Set image dimensions explicitly. The browser can reserve the image’s space before it downloads; without dimensions, an unloaded image can have zero size and cause content to shift when it appears. Provide meaningful alternative text for images and a descriptive title for frames, as in the examples.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Native loading="lazy" is a browser hint, not a precise application-controlled schedule. If an application needs custom behavior based on an element entering or leaving the viewport, the Intersection Observer API can provide that trigger. Use such custom logic only when the native hint does not meet the requirement.

Lazy-loaded routes and deferred UI in Angular

Load a component or child routes on navigation

Angular route configuration provides loadComponent for a lazy component and loadChildren for lazy child routes. Their loader functions commonly use dynamic imports. The imported file should resolve to the component or route configuration expected by the loader:

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

These loaders allow the build to produce separate chunks that are requested when the corresponding route becomes active. Eagerly load primary landing pages when they are needed immediately; routes such as infrequently visited reports or administration areas are stronger candidates for deferral.

Choose whether to preload lazy routes

Angular uses NoPreloading by default, so lazy modules are not automatically fetched in the background after initial navigation. Setting the router’s preloading strategy to PreloadAllModules fetches lazy modules after that first navigation. Preloading can reduce the wait when a user later visits a route, but it uses bandwidth and downloads code the user may never need. Nested lazy routes can also introduce multiple future requests, so consider the request sequence as well as the initial bundle.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Defer parts of a template with @defer

Angular’s @defer blocks can defer components, directives, and pipes in a template, allowing their code to be split into separate files and loaded after the rest of the template. What is actually deferred depends on Angular’s standalone and reference constraints: placing something in a block alone does not guarantee that it becomes a separately deferred dependency. Use this approach for a distinct piece of UI that is not needed for the initial view, and provide a suitable placeholder or loading treatment for the time before it appears.

Lazy-load components in React

React’s lazy function defers a component’s code until React first renders that component. The loader should return a promise that resolves to a module whose default export is the component. Wrap the lazy component in Suspense so the UI has a fallback while the module loads:

import { lazy, Suspense } from 'react';

const MarkdownPreview = lazy(() => import('./MarkdownPreview.js'));

export default function Page() {
  return (
    <Suspense fallback={<p>Loading preview…</p>}>
      <MarkdownPreview />
    </Suspense>
  );
}

If the imported module exports the component by name instead of as its default, adapt the loader to return a module object with that component as default. React caches the loader promise and the resolved component. If loading rejects, the error propagates to the nearest Error Boundary, so the loading fallback is not a substitute for an error state.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Choose a trigger that matches when the resource is needed

What is deferred Typical trigger Useful fit Cost to account for
JavaScript module User interaction or another condition that calls import() A heavy editor, chart, or tool used only by some users The first use waits for the module request and needs a loading and failure path.
Angular route Navigation to a route configured with loadComponent or loadChildren Secondary pages that do not need to be part of the initial landing experience Navigation may trigger one or more requests; preloading trades bandwidth for less later waiting.
React component First render of a component declared with lazy A component that is not required in the initial render Suspense needs a useful fallback, and rejected loading needs an Error Boundary.
Image or frame Browser-controlled proximity to the viewport with loading="lazy" Non-critical media below the initial view Reserve image dimensions to prevent layout shifts; a late resource can still affect the experience when it comes into view.

The table describes common patterns, not a rule to defer everything in a category. For example, delaying an image that is essential to the initial view or a module required for immediate interaction may move work onto the critical path at the moment it matters most.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make deferred content usable while it loads

  • Keep the shell and primary landing content available without waiting for secondary features.
  • Give deferred interfaces an intentional loading state that explains what is happening without unexpectedly collapsing or rearranging the page.
  • Provide an error path for failed JavaScript loads; in React, place an Error Boundary where it can handle the relevant lazy component.
  • Ensure loading and error states remain understandable to assistive-technology users, and preserve meaningful image alternatives and frame titles.
  • Use preloading selectively when the expected navigation delay is more damaging than the extra bandwidth.

Measure whether lazy loading helps

Compare the target application before and after the change. Inspect initial and deferred bundle sizes, the network request waterfall, Largest Contentful Paint (LCP), and interaction responsiveness. Check both the initial visit and the later route or interaction that triggers deferred code. A smaller initial bundle can be a real improvement while a newly delayed interaction becomes worse; the useful result is the balance in the actual application, not the presence of a lazy-loading API.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.