The Challenge: SVGs, SEO, and Clean Markdown
SVGs (Scalable Vector Graphics) offer numerous benefits for website images: they’re crisp at any resolution, typically smaller in file size than raster images, and can be styled with CSS. However, when building a static site with Hugo, developers often face a dilemma:
- Link to external SVG files: Simple, but search engines can’t “see” the content of the image
- Paste SVG code directly into markdown: Great for SEO, but clutters your content files with markup
Today, I’ll show you how to get the best of both worlds by creating a simple Hugo shortcode that allows you to keep your SVGs as separate files while embedding them directly in your generated HTML.
Why Inline SVGs Matter for SEO
When you simply link to an SVG file like this:
1
The HTML output will be something like:
1<img src="diagram.svg" alt="My Diagram">
While functional, this approach means:
- Search engines can only see the alt text, not the content within the SVG
- You miss out on semantic information that might be present in the SVG
By contrast, embedding the actual SVG code in your HTML allows search engines to interpret:
- Text content within the SVG
- ARIA labels and descriptions
- Semantic grouping and structure
This can improve your content’s relevance for related search queries.
Creating a Custom SVG Shortcode
Here’s a simple but powerful Hugo shortcode that reads an SVG file and embeds its contents directly in your HTML:
1{{ $svg := .Get "src" }}
2{{ $path := "" }}
3
4{{/* Determine if this is a page bundle resource or a static file */}}
5{{ if $.Page.Resources.GetMatch $svg }}
6 {{ $path = ($.Page.Resources.GetMatch $svg).Content }}
7{{ else if fileExists (print "static/" $svg) }}
8 {{ $path = readFile (print "static/" $svg) }}
9{{ else }}
10 {{ errorf "SVG file %s not found" $svg }}
11{{ end }}
12
13{{ $path | safeHTML }}
Save this code in a file named svg.html
in your layouts/shortcodes/
directory.
How It Works
This shortcode is versatile and intelligent:
- It first checks if the SVG exists as a page resource (in the same folder as your post if you’re using page bundles)
- If not found there, it looks in your site’s static directory
- It reads the file contents and embeds them directly in your HTML
- If the file isn’t found, it provides a clear error message
Enabling Raw HTML in Hugo
For this shortcode to work, you need to enable raw HTML rendering in your Hugo
configuration. Add this to your config.toml
, hugo.toml
, or equivalent
YAML/JSON configuration:
1[markup.goldmark.renderer]
2 unsafe = true
This tells Hugo’s Markdown processor (Goldmark) to allow raw HTML in the output.
Using the Shortcode in Your Content
With the shortcode created and raw HTML enabled, using it in your markdown is simple:
1## My Amazing Diagram
2
3{{< svg src="diagram.svg" >}}
4
5As you can see in the diagram above...
Example with Page Bundles
If you’re using Hugo page bundles (which I recommend), your content structure might look like:
content/
posts/
my-post/
index.md // Your post content with the shortcode
diagram.svg // SVG file in the same directory
Example with Static Files
Alternatively, you can store your SVGs in the static directory:
static/
images/
diagrams/
diagram.svg
And reference them in your content:
1{{< svg src="images/diagrams/diagram.svg" >}}
Extending the Shortcode
This basic shortcode covers the essentials, but you might want to extend it with additional features:
Adding CSS Classes
1{{ $svg := .Get "src" }}
2{{ $class := .Get "class" | default "" }}
3{{ $path := "" }}
4
5{{/* Determine if this is a page bundle resource or a static file */}}
6{{ if $.Page.Resources.GetMatch $svg }}
7 {{ $path = ($.Page.Resources.GetMatch $svg).Content }}
8{{ else if fileExists (print "static/" $svg) }}
9 {{ $path = readFile (print "static/" $svg) }}
10{{ else }}
11 {{ errorf "SVG file %s not found" $svg }}
12{{ end }}
13
14{{/* Add a wrapper with the specified class if provided */}}
15{{ if $class }}
16<div class="{{ $class }}">
17 {{ $path | safeHTML }}
18</div>
19{{ else }}
20 {{ $path | safeHTML }}
21{{ end }}
Usage:
1{{< svg src="diagram.svg" class="large-diagram centered" >}}
Adding Title and Description for Accessibility
For even better SEO and accessibility, we can add title and description attributes:
1{{ $svg := .Get "src" }}
2{{ $title := .Get "title" | default "" }}
3{{ $description := .Get "description" | default "" }}
4{{ $path := "" }}
5
6{{/* Get SVG content */}}
7{{ if $.Page.Resources.GetMatch $svg }}
8 {{ $path = ($.Page.Resources.GetMatch $svg).Content }}
9{{ else if fileExists (print "static/" $svg) }}
10 {{ $path = readFile (print "static/" $svg) }}
11{{ else }}
12 {{ errorf "SVG file %s not found" $svg }}
13{{ end }}
14
15{{/* Process SVG to add title and description */}}
16{{ $processed := $path }}
17{{ $labelIDs := slice }}
18
19{{/* Prepare aria-labelledby attribute if needed */}}
20{{ if $title }}
21 {{ $labelIDs = $labelIDs | append (printf "title-%s" $svg) }}
22{{ end }}
23{{ if $description }}
24 {{ $labelIDs = $labelIDs | append (printf "description-%s" $svg) }}
25{{ end }}
26
27{{/* Add role and aria-labelledby if we have any labels */}}
28{{ if gt (len $labelIDs) 0 }}
29 {{ $labelIDsString := delimit $labelIDs " " }}
30 {{ $processed = replace $processed "<svg " (printf "<svg role=\"img\" aria-labelledby=\"%s\" " $labelIDsString) }}
31{{ end }}
32
33{{/* Add title and description elements if provided */}}
34{{ if or $title $description }}
35 {{ $insertContent := "" }}
36 {{ if $title }}
37 {{ $insertContent = printf "%s<title id=\"title-%s\">%s</title>" $insertContent $svg $title }}
38 {{ end }}
39 {{ if $description }}
40 {{ $insertContent = printf "%s<desc id=\"description-%s\">%s</desc>" $insertContent $svg $description }}
41 {{ end }}
42 {{ $processed = replace $processed ">" (printf ">%s" $insertContent) 1 }}
43{{ end }}
44
45{{ $processed | safeHTML }}
Usage:
1{{< svg src="diagram.svg" title="System Architecture" description="Diagram showing the relationship between the frontend, API, and database layers" >}}
Performance Considerations
While inline SVGs are great for SEO, they do increase the initial HTML size of your page. Consider the following strategies if you use many SVGs:
- Optimize your SVGs with tools like SVGO before adding them to your site
- Use lazy loading for SVGs that appear further down the page
- Consider using a JavaScript solution to inject SVGs for non-critical images if page size becomes an issue
Conclusion
This custom SVG shortcode offers the perfect balance between maintainability and SEO optimization. Your markdown files stay clean and focused on content, while the generated HTML includes the full SVG code for search engines to interpret.
By taking advantage of Hugo’s powerful shortcode system, you can significantly improve your site’s SEO when using SVG images, potentially leading to better rankings and more traffic to your content.
Happy Hugo coding!
Have questions or suggestions about this approach? Drop a comment below or reach out on 𝕏.