Exposing your email in plain text on your website is a surefire way to get spammed into oblivion.

There are hundreds of thousands, if not millions, of scrapers working around the clock 24/7 to swipe your personal info. They’ll crawl your site, parse the HTML, and pattern match anything that looks remotely like an email address.

Soon enough, you’ll either be thanking your email host for investing in a good spam filter or banging your head against the wall due to the impending onslaught of SEO service inquiries.

The #1 Defense Against Email Scrapers Link to this heading

Luckily, if you’re a Hugo user, there’s a simple two-step process you can use to prevent bad actors from scraping your site entirely.

Hugo provides a built-in template function that allows you to base64 encode and decode content with a simple pipe filter.

In this quick tutorial, I’m going to show you how I combined this feature with a basic JavaScript function to throw the scrapers off their tracks for good.

Encoding Your Content in Hugo Templates Link to this heading

The encoding process is simple. For the sake of illustration, let’s say you have a few site params that Hugo reads your email and phone number from conditionally.

Here’s what it might look like in a vulnerable state:

1{{ with .Site.Params.myEmailAddress }}
2  <a href="mailto:{{.}}">{{.}}</a>
3{{ end }}
5{{ with .Site.Params.myPhoneNumber }}
6  <a href="tel:{{.}}">{{.}}</a>
7{{ end }}

The above would output something like this during the build process.

1<a href="mailto:user@example.com">user@example.com</a>
2<a href="tel:+1-111-111-1111">+1-111-111-1111</a>.

Not good. This is easy pickings for even your most rudimentary HTML parser.

Let’s start by making it a little bit harder.

To encode the values, simply append a piped base64Encode to your context or variable. I’ve also added a .needs-decoding CSS class and an incomplete mailto: / tel: to the elements in the template. You’ll see why in a second.

1{{ with .Site.Params.myEmailAddress }}
2  <a href="mailto:" class="needs-decoding">{{ . | base64Encode }}</a>
3{{ end }}
5{{ with .Site.Params.myPhoneNumber }}
6  <a href="tel:" class="needs-decoding">{{ . | base64Encode }}</a>
7{{ end }}

You should see the result of the encoding displayed on your page at this point. That’s not what we want either. We only want robots to see the encoded value, while regular users are shown your email address and phone number.

To fix this, all we need to do is write some JavaScript to decode the string dynamically.

Dynamically Decoding The base64 String Link to this heading

There are two things we need to think about here. First, we need to select all the elements with the .needs-decoding class.

Let’s start writing our function.

1function decodeStringBase64() {
2  let elemsToDecode = document.querySelectorAll('.needs-decoding');

Next, we’re going to iterate through all the elements with encoded content using forEach(). If the NodeList is empty, it should fail silently since we called the method on a valid type.

In our loop, we first replace the innerText of the element with the decoded string, regardless of what type of element it is. This way, if we have a span with an address but no link, it will still work.

atob() is the built-in that does all the fancy decoding for us behind the scenes.

Lastly, for just <a> elements, we conditionally check if the href attribute is equal to tel: or mailto:, just how we set it up in our template. Then we replace the href attribute with the prefix plus the newly decoded string for the respective values.

 1function decodeStringBase64() {
 2  let elemsToDecode = document.querySelectorAll('.needs-decoding');
 4  elemsToDecode.forEach(function(elem) {
 5    elem.innerText = atob(elem.innerText);
 6    if (elem.nodeName && elem.nodeName.toLowerCase() === 'a') {        
 7      if (elem.href === 'tel:') {
 8        elem.href = 'tel:' + elem.innerText
 9      } else if (elem.href === 'mailto:') {
10        elem.href = 'mailto:' + elem.innerText
11      }
12    }
13  });

That’s all there is to it! It’s not the prettiest JS in the world, but it’ll serve our purpose just fine.

Now just call your function somewhere with decodeStringBase64(), rebuild your site if necessary (it shouldn’t be), and fire up your page. You should see the decoded version of your email and phone number right there in your content for your viewers’ reading pleasure.

Happy Decoding Link to this heading

I kept this tutorial relatively simple on purpose so those with more backend experience than frontend like myself can start implementing this right away.

Hopefully, now you feel a bit more comfortable putting your contact information out online.