SVGs are everywhere on the web: icons, illustrations, logos, charts. But we rarely stop to think that how we load them has a real performance impact. An inline SVG in the HTML is not the same as one referenced from an <img>: caching, rendering, network requests and even how the browser prioritises them all differ.
In this article I go through the main SVG loading techniques from a performance perspective, covering their advantages, limitations and the most common anti-patterns I find in audits.
Table of Contents
Open Table of Contents
- Ways to load an SVG
- Inline SVG: full control, no caching
<img>: simple, cached, but opaque<object>: embedded document- CSS
background-image: decorative SVGs - SVG sprite +
<use>: one request, reusable, styleable - Anti-pattern: raster images embedded in SVG
- How to detect it
- Performance comparison: when to use each method
- Conclusion
Ways to load an SVG
There are five main ways to include an SVG in a web page, and each behaves differently:
| Method | Cache | Network request | CSS/JS access | Reusable |
|---|---|---|---|---|
Inline <svg> | ❌ No | ❌ No | ✅ Yes | ❌ No |
<img src="icon.svg"> | ✅ Yes | ✅ Yes | ❌ No | ✅ Yes |
<object data="icon.svg"> | ✅ Yes | ✅ Yes | Limited | ✅ Yes |
CSS background-image | ✅ Yes | ✅ Yes | ❌ No | ✅ Yes |
SVG sprite + <use> | ✅ Partial | ✅ Yes | ✅ Yes | ✅ Yes |
Inline SVG: full control, no caching
Embedding the SVG directly in the HTML gives the most control: full CSS and JavaScript access, no extra network requests, compatible with animations and currentColor.
<!-- ✅ Benefits: CSS/JS access, no network request -->
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path d="M12 2L2 7l10 5 10-5-10-5z" />
</svg>
The problem: every time the page loads, the SVG is parsed again. There is no cache for the SVG itself — it is embedded in the HTML. If the same icon appears across 50 different pages, it is downloaded and parsed 50 times.
<!-- ❌ Repeating the same SVG on every page has a parsing cost -->
<!-- page-1.html -->
<svg>...</svg>
<!-- page-2.html -->
<svg>...</svg>
<!-- the browser does not cache it across pages -->
When it makes sense: unique SVGs per page (logos, hero illustrations), when we need to animate them with CSS or JS, or when the SVG is the LCP candidate and we want to avoid the network request.
<img>: simple, cached, but opaque
Referencing an SVG from <img> is the cleanest approach for icons and graphics that do not need interaction:
<!-- ✅ Cached, does not block the parser -->
<img src="/icons/arrow.svg" width="24" height="24" alt="Next" loading="lazy" />
The browser treats it like any image: it caches it, prioritises it according to loading and fetchpriority, and can share it across pages. The SVG has no access to the document’s styles: currentColor does not work, and it is not possible to manipulate it with JavaScript.
<!-- ❌ currentColor does not inherit the document colour -->
<img src="/icons/icon.svg" style="color: red" />
<!-- The SVG fill will not change -->
<object>: embedded document
<object> loads the SVG as an independent document with its own context. It has caching, but access from the parent document is limited and requires extra scripting. In practice, it is the least-used option and comes with the most friction.
<object data="/graphic.svg" type="image/svg+xml" width="200" height="200">
<!-- Fallback for unsupported browsers -->
<img src="/graphic.png" alt="Graphic" />
</object>
For most cases, <img> or inline are better alternatives.
CSS background-image: decorative SVGs
For purely decorative SVGs (dividers, patterns, backgrounds) that need no alternative text or interaction, background-image is a valid choice:
/* ✅ For decorative elements */
.divider {
background-image: url("/decorative/wave.svg");
background-size: cover;
height: 80px;
}
No CSS or JS access to the SVG’s internal content, and no semantics for screen readers. Fine for decoration, wrong for meaningful icons.
SVG sprite + <use>: one request, reusable, styleable
The SVG sprite is the most efficient technique for reusable icons: a single file containing all the symbols, referenced via <use>. One request, cached, shared across pages.
<!-- sprite.svg (loaded once) -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
<symbol id="icon-arrow" viewBox="0 0 24 24">
<path fill="currentColor" d="M5 12h14M12 5l7 7-7 7" />
</symbol>
<symbol id="icon-close" viewBox="0 0 24 24">
<path fill="currentColor" d="M18 6L6 18M6 6l12 12" />
</symbol>
</svg>
<!-- Used anywhere in the HTML -->
<svg width="24" height="24" aria-hidden="true">
<use href="/sprite.svg#icon-arrow" />
</svg>
The sprite is cached by the browser. Each <use> only references a symbol — it does not re-download the file. It also retains currentColor support for CSS colouring, as long as the SVG paths use fill="currentColor".
/* ✅ currentColor works with <use> if the SVG uses fill="currentColor" */
.icon {
color: var(--color-primary); /* inherited as currentColor */
}
One limitation to keep in mind: <use> with an external reference (href pointing to another domain) may have CORS restrictions.
Anti-pattern: raster images embedded in SVG
This is one of the anti-patterns with the greatest performance impact, and one I come across frequently in audits. An SVG can contain raster images (WebP, JPEG, PNG) encoded as base64 inside the file itself:
<!-- ❌ Anti-pattern: raster image embedded in SVG -->
<svg xmlns="http://www.w3.org/2000/svg">
<image href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..." />
</svg>
Why is it a problem?
- File size explodes: base64 adds a 33% overhead to the file weight
- No optimisation: the browser cannot apply adaptive compression or format negotiation (AVIF, WebP)
- No granular caching: the raster image is not cached separately; if it changes, the entire SVG is invalidated
- Blocks rendering: the SVG does not render until the embedded image is fully downloaded and decoded
<!-- ✅ External reference: the image is cached and optimised separately -->
<svg xmlns="http://www.w3.org/2000/svg">
<image href="/photos/team-photo.jpg" width="800" height="600" />
</svg>
Design tool exporters such as Figma or Illustrator sometimes produce this pattern when the design includes raster images. Always check before publishing.
How to detect it
In DevTools you can see the actual size of SVGs in the Network panel. An SVG over 50KB is usually suspicious. To analyse it directly in the browser, the SVG Embedded Bitmap Analysis snippet from WebPerf Snippets scans all SVGs on the page and identifies those containing embedded raster images. You can also search the project files:
# Find SVGs with base64-embedded images
grep -rl "data:image" ./public --include="*.svg"
Performance comparison: when to use each method
- Inline SVG → LCP candidates, unique SVGs with CSS/JS animation
<img>→ Icons and graphics without interaction, with lazy loading- SVG sprite +
<use>→ Reusable icon systems across the whole site background-image→ Purely decorative elements<object>→ Very specific cases, generally avoidable
Conclusion
The choice of SVG loading method is not cosmetic: it directly affects caching, number of requests, parsing and perceived performance. The sprite with <use> is the most efficient option for icon systems. For unique illustrations with animation, inline makes sense. And always — always — avoid embedding raster images inside SVGs.