Lazy Loading Beyond Images

Performance & Core Web Vitals — Lazy Loading Beyond Images
Performance & Core Web Vitals
Chapter 6 · Lazy Loading Beyond Images

📦 Lazy Loading Beyond Images

Chapter 5 covered lazy loading for images. This chapter widens the same "defer what's not immediately needed" principle to JavaScript itself — code splitting and dynamic imports.

The Problem: One Giant Bundle

If an entire app's JavaScript — every route, every component, every feature — ships as one bundle, every visitor pays the download, parse, and execute cost for code they may never use on this particular visit. This directly worsens both metrics already covered: bandwidth competition with the LCP resource (Chapter 2), and a large bundle's own execution frequently qualifying as a long task on its own (Chapter 4).

✂️ Code Splitting

Breaking one bundle into smaller chunks that load independently. Route-based splitting — each page getting its own chunk, loaded only when that route is actually visited — is the most common and usually highest-impact first step.

Dynamic Imports

The import() syntax loads a module on demand, rather than at initial parse time:

Loading a Component Only When Needed

loadCommentsButton.addEventListener("click", async () => { const { renderComments } = await import("./comments.js"); // fetched only now renderComments(); });

Framework-level APIs build on this same mechanism — React.lazy + Suspense, Vue's defineAsyncComponent — all ultimately compiling down to a dynamic import() per chunk.

Lazy-Loading Below-the-Fold Components

A heavy component — a comments section, a complex chart, a modal that isn't open by default — sitting below the fold doesn't need to ship in the initial bundle at all. Load it only when it's about to become visible (often paired with Chapter 5's IntersectionObserver) or in response to a user action that reveals it.

Route vs Component Granularity

Route-level splitting is usually the highest-leverage first step — coarse, simple, big impact. Component-level splitting adds finer control for individual heavy pieces within a single route.

The Placeholder Needs Reserved Space Too

A lazy-loaded region needs a placeholder while its code downloads — if that placeholder is a different size than the eventual real component, it reintroduces exactly the layout shift Chapter 3 covered for images, just for a component instead.

Monolithic Bundle vs Split Loading

One Bundle

Every route and feature downloads, parses, and executes upfront — regardless of what this specific visit actually needs.

Route + Component Splitting

Only this route's code loads now; heavy below-the-fold pieces load only when they're about to matter.

⚖️ The Tradeoff Against LCP

Never lazy-load anything needed for the initial, above-the-fold render — the JavaScript equivalent of Chapter 5's "don't lazy-load the LCP image" gotcha, generalized. Lazy-loading a component that's actually part of the LCP element (or whose absence causes a shift once it pops in) actively hurts the exact metrics this course is about. The right split line is the same above/below-the-fold boundary already established for images.

💻 Coding Challenges

Challenge 1: Dynamically Import a Chart Component

Write code that dynamically imports a renderChart function from ./chart.js only when a <div id="chart-section"> scrolls into view, using an IntersectionObserver.

Goal: Practice combining Chapter 5's IntersectionObserver pattern with a dynamic import.

→ Solution

Challenge 2: Reserve Space for a Lazy-Loaded Placeholder

A lazy-loaded comments section's real content is roughly 400px tall once loaded, but its placeholder currently has no explicit height. Write the CSS fix, explaining which earlier chapter's principle it applies.

Goal: Practice recognizing that lazy-loaded components need the same reserved-space treatment as images.

→ Solution

Challenge 3: Decide What Not to Lazy-Load

A page's hero section includes a headline, a hero image, and an interactive pricing calculator widget directly beneath the hero image, all visible without scrolling. Identify which of these should NOT be lazy-loaded and explain why, using this chapter's tradeoff section.

Goal: Practice applying the above-the-fold boundary correctly to a mixed set of elements.

→ Solution

⚠️ Gotcha: Lazy-Loading Something Needed for the Initial Paint

In an eager attempt to "improve performance," it's tempting to wrap nearly everything in a dynamic import — including components that are actually part of, or directly adjacent to, the above-the-fold content a visitor sees immediately. Doing this to anything contributing to the LCP element delays exactly the content Chapter 2 said needs to load as early as possible, and doing it to anything whose absence shifts the layout once it finally loads reintroduces Chapter 3's CLS problem. Lazy loading is a tool for what's not immediately needed — applying it to what clearly is needed doesn't improve performance, it actively damages the two metrics this technique is meant to help.

🎯 What's Next

With loading strategy covered for both images and code, the next chapter turns to what happens once JavaScript is actually running: JavaScript Performance at the Page Level — debounce/throttle applied to scroll and resize handlers, and third-party script impact.