Recently, I had to make a web page displaying a bunch of SVG graphs for an analytics dashboard. I used a bunch of <rect>
, <line>
and <text>
elements on each graph to visualize certain metrics.
This works and renders just fine, but results in a bloated DOM tree, where each shape is represented as separate nodes. Displaying all 50 graphs simultaneously on a web page results in 5,951 DOM elements in total, which is far too many.

This is not optimal for several reasons:
- A large DOM increases memory usage, longer style calculations, and costly layout reflows.
- It will increases the size of the file on the client side.
- Lighthouse penalizes the performance and SEO scores.
- Maintainability is a nightmare — even if we use a templating system — because there’s still a lot of cruft and repetition.
- It doesn’t scale. Adding more graphs only exacerbates these issues.
If we take a closer look at the graphs, we can see a lot of repeated elements.

Here’s dummy markup that’s similar to the graphs we’re using:
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="500"
height="200"
viewBox="0 0 500 200"
>
<!--
📊 Render our graph bars as boxes to visualise our data.
This part is different for each graph, since each of them displays different sets of data.
-->
<g class="graph-data">
<rect x="10" y="20" width="10" height="80" fill="#e74c3c" />
<rect x="30" y="20" width="10" height="30" fill="#16a085" />
<rect x="50" y="20" width="10" height="44" fill="#16a085" />
<rect x="70" y="20" width="10" height="110" fill="#e74c3c" />
<!-- Render the rest of the graph boxes ... -->
</g>
<!--
Render our graph footer lines and labels.
-->
<g class="graph-footer">
<!-- Left side labels -->
<text x="10" y="40" fill="white">400k</text>
<text x="10" y="60" fill="white">300k</text>
<text x="10" y="80" fill="white">200k</text>
<!-- Footer labels -->
<text x="10" y="190" fill="white">01</text>
<text x="30" y="190" fill="white">11</text>
<text x="50" y="190" fill="white">21</text>
<!-- Footer lines -->
<line x1="2" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<line x1="4" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<line x1="6" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<line x1="8" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<!-- Rest of the footer lines... -->
</g>
</svg>
And here is a live demo. While the page renders fine the graph’s footer markup is constantly redeclared and all of the DOM nodes are duplicated.
The solution? The SVG element.
Luckily for us, SVG has a <use>
tag that lets us declare something like our graph footer just once and then simply reference it from anywhere on the page to render it as many times as we want. From MDN:
The
<use>
element takes nodes from within the SVG document, and duplicates them somewhere else. The effect is the same as if the nodes were deeply cloned into a non-exposed DOM, then pasted where theuse
element is.
That’s exactly what we want! In a sense, <use>
is like a modular component, allowing us to drop instances of the same element anywhere we’d like. But instead of props and such to populate the content, we reference which part of the SVG file we want to display. For those of you familiar with graphics programming APIs, such as WebGL, a good analogy would be Geometry Instancing. We declare the thing we want to draw once and then can keep reusing it as a reference, while being able to change the position, scale, rotation and colors of each instance.
Instead of drawing the footer lines and labels of our graph individually for each graph instance then redeclaring it over and over with new markup, we can render the graph once in a separate SVG and simply start referencing it when needed. The <use>
tag allows us to reference elements from other inline SVG elements just fine.
Let’s put it to use
We’re going to move the SVG group for the graph footer — <g class="graph-footer">
— to a separate <svg>
element on the page. It won’t be visible on the front end. Instead, this <svg>
will be hidden with display: none
and only contain a bunch of <defs>
.
And what exactly is the <defs>
element? MDN to the rescue once again:
The
<defs>
element is used to store graphical objects that will be used at a later time. Objects created inside a<defs>
element are not rendered directly. To display them you have to reference them (with a<use>
element for example).
Armed with that information, here’s the updated SVG code. We’re going to drop it right at the top of the page. If you’re templating, this would go in some sort of global template, like a header, so it’s included everywhere.
<!--
⚠️ Notice how we visually hide the SVG containing the reference graphic with display: none;
This is to prevent it from occupying empty space on our page. The graphic will work just fine and we will be able to reference it from elsewhere on our page
-->
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="500"
height="200"
viewBox="0 0 500 200"
style="display: none;"
>
<!--
By wrapping our reference graphic in a <defs> tag we will make sure it does not get rendered here, only when it's referenced
-->
<defs>
<g id="graph-footer">
<!-- Left side labels -->
<text x="10" y="40" fill="white">400k</text>
<text x="10" y="60" fill="white">300k</text>
<text x="10" y="80" fill="white">200k</text>
<!-- Footer labels -->
<text x="10" y="190" fill="white">01</text>
<text x="30" y="190" fill="white">11</text>
<text x="50" y="190" fill="white">21</text>
<!-- Footer lines -->
<line x1="2" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<line x1="4" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<line x1="6" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<line x1="8" y1="195" x2="2" y2="200" stroke="white" strokeWidth="1" />
<!-- Rest of the footer lines... -->
</g>
</defs>
</svg>
Notice that we gave our group an ID of graph-footer
. This is important, as it is the hook for when we reach for <use>
.
So, what we do is drop another <svg>
on the page that includes the graph data it needs, but then reference #graph-footer
in <use>
to render the footer of the graph. This way, there’s no need to redeclaring the code for the footer for every single graph.
Look how how much cleaner the code for a graph instance is when <use>
is in.. umm, use.
<svg
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="500"
height="200"
viewBox="0 0 500 200"
>
<!--
📊 Render our graph bars as boxes to visualise our data.
This part is different for each graph, since each of them displays different sets of data.
-->
<g class="graph-data">
<rect x="10" y="20" width="10" height="80" fill="#e74c3c" />
<rect x="30" y="20" width="10" height="30" fill="#16a085" />
<rect x="50" y="20" width="10" height="44" fill="#16a085" />
<rect x="70" y="20" width="10" height="110" fill="#e74c3c" />
<!-- Render the rest of the graph boxes ... -->
</g>
<!--
Render our graph footer lines and labels.
-->
<use xlink:href="graph-footer" x="0" y="0" />
</svg>
And here is an updated <use>
example with no visual change:
Problem solved.
What, you want proof? Let’s compare the demo with <use>
version against the original one.
DOM nodes | File size | File Size (GZIP compression) | Memory usage | |
---|---|---|---|---|
No <use> | 5,952 | 664 KB | 40.8 KB | 20 MB |
With <use> | 2,572 | 294 KB | 40.4 KB | 18 MB |
Savings | 56% fewer nodes | 42% smaller | 0.98% smaller | 10% less |
As you can see, the <use>
element comes in handy. And, even though the performance benefits were the main focus here, just the fact that it reduces huge chunks of code from the markup makes for a much better developer experience when it comes to maintaining the thing. Double win!
I’m yet to delve into the world of svg but this looks fantastic, great article!
Thanks for this awesome input! I feel like we could be doing a lot more with those svgs than we currently are. (I know I am)
Merci!
You might also investigate using
path
elements and rendering all like-colored bars as a single path. Might not be an option if you want the bars to be individually interactive though, like with a tooltip.This is one of the best SVG features and the basis for SVG spritemaps. However, one caveat: if you are hosting your SVGs on another domain than they will be served on, is not yet fully CORS compliant, so it won’t render anything. Bummer.
I noticed in MDN, xlink:href will be deprecated in svg2, so should we just use href? Does it work the same way?
That’s right, it is depricated and you should use inly href insead xlink:href.
You could also improve the readability and maintainability by using Web Components. You could create a custom element like
<metric-graph points="...">
and dynamically generate the SVG (with<use>
, of course!). This way, a set of graphs may look like this:<article class="graph-grid"><metric-graph points="-0.5 7.2 3.1"/><metric-graph points="1.2 -2.4 3.8"/></article>
+1 for doing it with Web Components
The idea looks quite interesting, and perhaps you could push it further :
If you have a server side language in your page building (php, python, whatever), you probably get your raw data as an array or list.
You could test the following :
1) for every rectangle you have to draw, you calculate an id, telling color and height of the rectangle.
2) if this id has already been previously calculated, you use it. If not, you draw it, and put an id on it.
You can even store it in the “display none svg” only if it’s used at least twice, and not if it’s unique, to maximize node and code size savings.
Yep, SVG’s “use” element is very useful! For example in this demo, every dot is a use-element:
https://tobireif.com/demos/snake_pattern/
Hello, shouldn’t your
use
href attribute begin with the pound sign like<use xlink:href="#graph-footer" x="0" y="0" />
? I believe that’s how I’ve seen it in other tutorials.Great article! Thanks, Georgi!
hey,
someone made npm package for svg icons inspired by this article
https://www.npmjs.com/package/@novyk/ikong
Thats so cool!! Thanks for letting me know, I 100% would have missed it otherwise