Email Obfuscation: A Hugo Shortcode and Astro Component

Email Obfuscation: A Hugo Shortcode and Astro Component

The article Email Obfuscation: What Works in 2024 by Spencer Mortensen discusses various techniques to hide email addresses from spam bots while keeping them accessible to users. It evaluates methods like plain text, HTML entities, CSS display properties, JavaScript techniques, and others, testing their effectiveness in blocking spam.

Some methods, such as CSS display: none and certain JavaScript techniques, are found to be highly effective, while others like HTML comments and symbol substitution offer little protection.

CSS display: none

This [technique] requires the harvester to interpret style rules contained in a separate stylesheet document. This is impossible for the vast majority of harvesters, which are something less than a full web browser. As a result, this is one of the best obfuscation techniques.

Use the following HTML:

<span class="email">email@example<b>.example</b>.com</span>

And the following CSS:

span.email b {
display: none;
}

It’s important to use “display: none” to hide elements of the email address, because this is fully supported by all screen readers. Visual-only techniques (such as repositioning part of the text off screen) will prevent everyone except your sighted readers from reaching you.

Hugo Shortcode

For my Hugo websites, I converted this technique into a Hugo shortcode. Here’s a step-by-step guide on how I accomplished it.

First, add a default email address to config/_default/params.toml — we will use this as a fallback when no email address is specified:

# defaultEmail
defaultEmail = "email@example.com"

Next, add the CSS — to assets/scss/common/_custom.scss if you’re using Hyas:

span.email b {
display: none;
}

Then, create the shortcode file:

layouts/shortcodes/email.html
{{- /* Set defaults and get args. */}}
{{- $address := index .Params 0 | default site.Params.defaultEmail }}
{{- /* Get parts. */}}
{{- $addressParts := split $address "@" }}
{{- $userName := (index $addressParts 0) }}
{{- $rootDomain := (index $addressParts 1) }}
{{- $rootDomainParts := split $rootDomain "." }}
{{- $domainName := (index $rootDomainParts 0) }}
{{- $topLevelDomain := (index $rootDomainParts 1) }}
{{- /* Render. */}}
<span class="email">
{{- printf "%s@%s<b>.%s</b>.%s" $userName $domainName $domainName $topLevelDomain | safeHTML -}}
</span>

The shortcode gets the email address you provided — using the default email address if you didn’t specify one. Next, it splits the email address in parts — userName, domainName, and topLevelDomain — and renders the HTML.

Now, you can use the shortcode in Markdown — using defaultEmail:

{{< email >}}

Or, by specifying an email address:

{{< email "email@example.com" >}}

Astro Component

I also converted this technique into an Astro component for my Astro websites.

First, add a default email address to src/params.ts — we will use this as a fallback when no email address is specified:

export type siteParams = {
defaultEmail: string;
};
const siteParams: siteParams = {
defaultEmail: "email@example.com"
}
export default siteParams;

Next, add the CSS to src/styles/global.css:

span.email b {
display: none;
}

Then, create the component file:

src/components/Email.astro
---
// Set defaults and get args
import siteParams from '../params';
const defaultEmail = siteParams.defaultEmail;
const { address = defaultEmail } = Astro.props;
// Get parts
const addressParts = address.split('@');
const userName = addressParts[0];
const rootDomain = addressParts[1];
const rootDomainParts = rootDomain.split('.');
const domainName = rootDomainParts[0];
const topLevelDomain = rootDomainParts[1];
---
<!-- Render -->
<span class="email">{userName}@{domainName}<b>.{domainName}</b>.{topLevelDomain}</span>

The component gets the email address you provided — using the default email address if you didn’t specify one. Next, it splits the email address in parts — userName, domainName, and topLevelDomain — and renders the HTML.

Now, you can use the component in MDX — using defaultEmail:

import Email from '../components/Email.astro';
<Email />

Or, by specifying an email address:

import Email from '../components/Email.astro';
<Email address="email@example.com" />

Conclusion

Spencer Mortensen showed us that some email obfuscation methods, such as CSS display: none and certain JavaScript techniques, are found to be highly effective, while others like HTML comments and symbol substitution offer little protection.

I’ve explained how to use the CSS display: none technique to hide email addresses from spam bots, and showed you how to convert this technique into a Hugo shortcode and an Astro component — I hope you find it useful!

Subscribe to Henk's Newsletter

Get tips, technical guides, and best practices. Once a month. Right in your inbox.