Optimizing SVGs for SEO in Hugo with a Custom Shortcode

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:

  1. Link to external SVG files: Simple, but search engines can’t “see” the content of the image
  2. 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![My Diagram](diagram.svg)

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:

  1. It first checks if the SVG exists as a page resource (in the same folder as your post if you’re using page bundles)
  2. If not found there, it looks in your site’s static directory
  3. It reads the file contents and embeds them directly in your HTML
  4. 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:

  1. Optimize your SVGs with tools like SVGO before adding them to your site
  2. Use lazy loading for SVGs that appear further down the page
  3. 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 𝕏.

Share this post