Lazy Loading and Dynamic Import
Lazy loading means loading something only when it is needed.
This can improve startup performance by reducing the amount of work done up front.
Why Lazy Loading Helps
When a page loads, the browser may need to download, parse, compile, and run JavaScript.
If you load code for every feature immediately, the first load can become slow.
Lazy loading can defer work for:
- rarely used screens
- admin-only features
- large libraries
- below-the-fold images
- optional widgets
Dynamic import()
JavaScript modules can be loaded dynamically with import().
button.addEventListener("click", async () => {
const module = await import("./export-report.js");
module.exportReport();
});The module is loaded when the button is clicked.
This is different from a static import.
import { exportReport } from "./export-report.js";Static imports are loaded before the module runs.
Loading a Heavy Library When Needed
async function showChart(data) {
const { createChart } = await import("./chart-library.js");
createChart("#chart", data);
}If charts are not always shown, this can reduce initial JavaScript cost.
Handle Loading States
Lazy loading introduces waiting.
Show the user what is happening.
async function openSettings() {
settingsPanel.textContent = "Loading settings...";
const { renderSettings } = await import("./settings-panel.js");
renderSettings(settingsPanel);
}Without a loading state, the interface may feel broken.
Handle Errors
Dynamic imports can fail if the network fails or the file cannot be loaded.
async function openEditor() {
try {
const { renderEditor } = await import("./editor.js");
renderEditor();
} catch (error) {
console.error(error);
showMessage("The editor could not be loaded. Please try again.");
}
}Always think about failure paths for lazy-loaded features.
Lazy Loading Images
Images can also be lazy loaded.
<img src="product.jpg" alt="Product photo" loading="lazy">The browser can delay loading the image until it is closer to the viewport.
This is useful for long pages with many images.
Do not lazy load the main hero image if it is important for the first screen.
Intersection Observer
IntersectionObserver lets you run code when an element enters the viewport.
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
loadCardDetails(entry.target);
observer.unobserve(entry.target);
}
}
});
for (const card of document.querySelectorAll(".product-card")) {
observer.observe(card);
}This can lazy load details, images, or widgets when they become relevant.
Do Not Lazy Load Everything
Lazy loading has tradeoffs.
It can:
- delay a feature when the user first needs it
- create more network requests
- make error handling more complex
- make code splitting harder to reason about
Important startup code should usually load immediately.
Rare or expensive features are better candidates for lazy loading.
Common Mistakes
- Lazy loading critical content needed for the first screen.
- Forgetting loading and error states.
- Splitting code into too many tiny files.
- Lazy loading a feature that almost every user needs immediately.
- Assuming smaller initial bundles always mean a faster overall experience.
Summary
Lazy loading defers work until it is needed.
Use dynamic import() for optional JavaScript and browser features like loading="lazy" or IntersectionObserver for content that can wait.
Balance faster startup against later loading delays.