Lazy Loading SVGs: When and How to Do It
Should you lazy load SVG icons? Learn the best techniques for deferring off-screen SVGs to improve initial page load performance.
Lazy loading raster images (<img loading="lazy">) is a standard practice for modern web development. But what about SVG icons?
SVGs are fundamentally different because they are usually inlined directly into the HTML or bundled into your JavaScript. This means standard lazy loading attributes don’t apply.
Here is a guide on when you should lazy load SVG icons, and the technical patterns to achieve it in modern frameworks.
When Should You Lazy Load SVGs?
Because individual SVGs are tiny (often < 1KB), lazy loading a single “Settings” icon in a dropdown menu is overkill. The JavaScript required to orchestrate the lazy load will weigh more than the icon itself.
You should only lazy load SVGs when:
- Massive Asset Grids: You have a page displaying hundreds of icons (e.g., an icon search directory, an emoji picker, or a massive product feature matrix).
- Below the Fold Illustrations: You are using massive, highly complex SVGs (like hero illustrations exceeding 50KB) that are not immediately visible.
- Heavy Component Payloads: The SVGs are part of a massive interactive component (like an embedded interactive map) that sits far below the fold.
Technique 1: Native Loading Attribute (For External SVGs)
If you are using SVGs via an <img> tag, the solution is trivial. Modern browsers support native lazy loading for external vector files just like JPEGs.
<!-- Defers downloading the external .svg file until it approaches the viewport -->
<img src="/icons/complex-chart.svg" alt="Analytics Chart" loading="lazy" width="800" height="400" />
Pros: Zero JavaScript required.
Cons: You lose the ability to style the SVG with CSS (e.g., fill="currentColor").
Technique 2: Dynamic Imports (React / Next.js)
If you are using Inline SVGs as React components, they are compiled into your JavaScript bundle. To lazy load them, you must code-split the bundle using Dynamic Imports.
In Next.js, this is achieved using next/dynamic.
import dynamic from 'next/dynamic';
// Code-splits the heavy illustration into its own JS chunk
const HeavyFooterIllustration = dynamic(() => import('@/components/illustrations/FooterArtwork'), {
loading: () => <div className="w-full h-64 bg-gray-100 animate-pulse"></div>,
ssr: false, // Optional: prevents rendering on the server if it's purely client-side
});
export default function Page() {
return (
<div>
{/* ... top of page content ... */}
{/* The JS for this SVG will only be fetched when the component mounts */}
<HeavyFooterIllustration />
</div>
);
}
Note that next/dynamic fetches the code when the component mounts, not necessarily when it enters the viewport. To fetch it only on scroll, you need to combine it with an IntersectionObserver hook (like react-intersection-observer).
Technique 3: The SVG <use> Sprite Strategy
If you have a directory of 500 icons, using 500 Dynamic Imports is a terrible idea—it will create 500 tiny JS network requests, crashing the browser’s request limit.
Instead, use an SVG Sprite and let the browser handle it.
<svg class="icon" width="24" height="24">
<use href="/sprites/massive-icon-pack.svg#icon-target"></use>
</svg>
Why this works like Lazy Loading:
When the browser encounters the <use> tag, it makes a single asynchronous network request to fetch /sprites/massive-icon-pack.svg. While it’s downloading, the rest of the HTML parses and renders instantly.
It naturally prevents the SVG path data from bloating your initial HTML document, acting as an asynchronous asset.
Summary
Do not prematurely optimize by lazy loading small UI icons like chevrons or menu bars. The overhead isn’t worth it.
However, for heavy SVG illustrations, leverage React’s dynamic imports. For massive grids of icons, abandon inline SVGs entirely and rely on the asynchronous nature of SVG Sprites (<use>).