I made the switch over to Hugo a short while and love the fast builds. You can read all about me ‘crushing’ on it in my What I Learned using Hugo post. There’s one main thing I want highlight in this post and it’s how to optimize Hugo for SEO. Hugo, just like any other CMS, is pretty good out of the box but you need to optimize it. Here are my Hugo SEO tips for optimization.

SEO Tip 1 - Run your site through Web.dev

My nephew alerted me to Web.dev which is an opensource SEO, Performance, Accessibility, and Best Practice analyzer. I ran my site through there, as well as my sections (blogs, tutorials, and reviews), and found some really valuable insight. I always knew that my images were clogging up my page load times but I never knew how bad they were.

I rank above a score of 90 for SEO, Performance, and Accessibility but Performance is what I need to work on. If you find the link, “view report” you should click on it and read it. It tells you everything you need to know and how to fix your site. I need to rethink my ‘featured image’ banner and how I insert images into my posts going forward and I suspect it will take me a few months to clean everything up. Hat tip to my nephew and his SEO consulting business!

SEO Tip 2 - Use a Content Delivery Network (CDN)

This was perhaps the easiest and fastest boost to all the web.dev categories was just hooking up a CDN to my blog. It was easy using my host provider Dreamhost.com. I forgot to mention in my review of Dreamhost that they also give you a basic membership to Cloudflare. I created an account with Cloudflare and go an instant boost in performance. On the flip side, one of the biggest boosts to performance would be generating different image sizes for different browser sizes. This was one of the recommendations from Web.dev and Cloudflare can do that automatically, if you buy a business subscription for $400/month. That’s a non-starter with me and I’ll work on the method that Adam posts about in his blog.

SEO Tip 3 - Image Compression

This ties back to Tips 1 and Tips 2 above but image compression makes a huge difference. Even if you have a text heavy site with the occasional image, it’s worth compressing them. I downloaded the open-source ImageOptim and ran it over my entire set of images in my /static folder. Mind you, the /static folder is what’s causing my entire performance problem. This folder is a leftover from my Wordpress, Blot, Jekyll, and Expression Engine days.

In Hugo, anything file in the /static folder gets uploaded so it’s really easy to just dump all your images and files in there and then link to them in your blog post. This will kill your performance over the long run and it’s been doing it to me. Hugo has a wonderful want to auto process images IF you use the right content organization. In Hugo, the content organization is fluid and it’s made porting this site over from Pelican easy. Once again, this easiness can trip you up in the long run if performance is what matters to you, so you need to spend some time thinking of content organization, which is SEO Tip 4 below.

SEO Tip 4 - Content Organization

A bit of warning here but Hugo’s online documentation is very terse, which is what the Go Lang spec is like. If you read between the lines they’re assuming you already know what you are doing and I find that attitude to be a barrier for deeper adoption of this awesome platform. Hopefully, as I write more about Hugo and build out tutorials, this will make it easier for readers to grasp.

Hugo believes that you organize your content with a purpose. The same structure that works to organize your source content is used to organize the rendered site. As displayed above, the organization of the source content will be mirrored in the destination. (via Hugo)

Start here on Hugo’s Content Organization page and zoom into this screenshot image:

Hugo Content Organization

This is how Hugo prefers to organize your content. On the surface, it’s very simple and easy, but you can easily misuse it. Especially when dealing with images. The trick is to add an /images folder below your /posts folder or whatever you name it. For example, my structure looks like this:

- content
    |- about/
        |- _index.md
    |- blog/
        |- 2020-05-28-SEO-for-Hugo.md
        |- other posts
    |- tutorials/
        |- rapidminer/
            |- rapidminer posts
        |- other tutorial posts
    |- reviews/
        |- review posts

What I need to correct is placing /images folders below the /blog, /tutorials, and /reviews sections because of Page Bundles. What are page bundles? They are awesome ways to process files, images, and templates in specific ways for a particular section. On my blog, the /blog, /tutorials, and /reviews are MY sections. Page bundles break out into Image Processing and Page Resources and this is where the magic happens.

The Image Processing ability of Hugo lets you automatically crop, resize, fill, save with lower quality, and much more by just putting the images in a folder that’s associated with your blog posts.

Using the Page Resources you can include and humanely process different file types to be associated with your particular post. I found this to be cool and it reminds me of what I wrote about content creation and management. Specifically this:

“ In essence, content types are the building blocks of dynamic, future-friendly content across systems. They provide the structure used by people and computers to explicitly express meaning.”

So what I need to do is add a /images and /files folder under each section. HOWEVER, I need to think about this because how I organize this going forward (and even apply to older posts) is going to have a huge impact on my performance, good or bad. Here’s my initial take on the future folder structure.

- content
    |- about/
        |- _index.md
        |- files/
        |- images/
    |- blog/
        |- 2020-05-28-SEO-for-Hugo.md
        |- other posts
        |- files/
        |- images/
    |- tutorials/
        |- rapidminer/
            |- rapidminer posts
            |- files/
            |- images/
        |- other tutorial posts
    |- reviews/
        |- review posts
        |- files/
        |- images/

You should spend time thinking about this too instead of just writing away without thinking about the end in mind (thanks Stephen Covey!).

SEO Tip 5 - Hugo Themes

The last SEO Tip I’ll leave you with is selecting an appropriate theme. On the surface, this sounds easy but if you navigate over to Hugo Themes you’ll see over 300 of them. Some look and respond better, and others are just simple and very basic. I’ve tried several of them and some have better RSS handling (something I’ll write about later) and others have better social media integration. Hugo has many shortcodes and internal templates to make social media easy but some templates take advantage of that better.

I opted for a mobile responsive with a beautiful featured image header that’s now eating into my performance. C’est La Vie. Still, you have to carefully review the templates or write your own.

SEO Tip 6 - Responsive Images!

A lot of CMS’s (like Wordpress) do this automatically when you upload an image but Hugo doesn’t do it out of the box. You can argue that Hugo doesn’t make it easy like Wordpress but a counterpoint would be you can make changes to image resizing on ‘the fly’ like in Hugo. I have no thoughts on what is right here, I’m just trying to show you how I did by learning it from other tutorials so you can use responsive images for Hugo and amp up your SEO.

Responsive Images

What are responsive images? They’re just two to three (even four or more) resized images of your original image that get automatically displayed on your webpage based on your browser window size. If you had an original image of 1024px x 1024px in size and you end up viewing your site from a mobile phone, you might see a smaller version of it (400px x 400px) served to you on the fly. This is achieved by using the srcset tag in your HTML code. Modern browsers can pick up on this tag and supply the correct image sizes on loading. You just have to reference multiple image sizes in our HTML code and then let the browser do it automatically.

Still, this presents two problems. One, you have to create two the three different sizes of your source image. Second, you have to then provide the reference HTML code to let the browser choose the right version. Since Hugo is a static generator, we can generate the HTML code automatically upon building time but how do you resize the images? You could manually do it but you’d be nuts to do that. The answer comes in the form of Hugo Page Resources. By creating a shortcode you can have Hugo resize the images and generate the correct HTML during your build time.

Laura’s and Adam’s tutorials

When I started learning about Hugo’s Page Sources and it’s built-in Image processing, I had no clue on how to use it. It wasn’t until I found Laura Kalbag’s and Adam Will’s tutorials that things began to make sense. Adam uses a javascript library to autogenerate his image sizes whereas Laura uses Hugo to take care of that as Resize operation with Page Resources. I predominately ended up using Laura’s way of resizing the images on the fly.

Content Organization

To use the image processing functions in Hugo, you will need to organize your content in a specific way. I wrote about content organization above and that’s what I started to do. I need to write a script to process all my old markdown posts, but I’ll relegate that to Python for another day.

You’ll need to first need to name your post index.md under a directory and show the image in the same folder, like below:

Content Folder

If you inspect the image above, you’ll notice I called an SVG type of image. Hugo can handle JPG, SVG, and PNG quite easily. I have not tested it out for WEBP files yet. Once you’ve done that, the next step is to write a shortcode to kick off the processing.

Shortcodes

I’ve come to love shortcodes, they’re amazing in the Hugo world. A shortcode is just that, a short word to handles some sort of processing behind the scenes. Hugo comes preloaded with some built-in ones like {{< youtube >}} or {{< instagram >}} which handles loading in videos, Instagram images, and other stuff. Well you can create your own and that’s what Laura did, she created the img shortcode that handled the Hugo resizing and auto-generating of the different image sizes.

The way she did was put in her markdown file the following shortcode:

{{< img src="name.jpg” alt="some alt caption” >}}

The img is the shortcode img.html but you can call it whatever you like. The processing is done in the img.html file and I used Laura’s as a template.

Her code, in it’s entirely is this:

{{/* get file that matches the filename as specified as src="" in shortcode */}}
{{ $src := .Page.Resources.GetMatch (printf "*%s*" (.Get "src")) }}

{{/* set image sizes, these are hardcoded for now, x dictates that images are resized to this width */}}

{{ $tinyw := default "500x" }}
{{ $smallw := default "800x" }}
{{ $mediumw := default "1200x" }}
{{ $largew := default "1500x" }}

{{/* resize the src image to the given sizes */}}

{{ .Scratch.Set "tiny" ($src.Resize $tinyw) }}
{{ .Scratch.Set "small" ($src.Resize $smallw) }}
{{ .Scratch.Set "medium" ($src.Resize $mediumw) }}
{{ .Scratch.Set "large" ($src.Resize $largew) }}

{{/* add the processed images to the scratch */}}

{{ $tiny := .Scratch.Get "tiny" }}
{{ $small := .Scratch.Get "small" }}
{{ $medium := .Scratch.Get "medium" }}
{{ $large := .Scratch.Get "large" }}

{{/* only use images smaller than or equal to the src (original) image size, as Hugo will upscale small images */}}
{{/* set the sizes attribute to (min-width: 35em) 1200px, 100vw unless overridden in shortcode */}}

<img loading = "lazy"
  {{ with .Get "sizes" }}sizes='{{.}}'{{ else }}sizes="(min-width: 35em) 1200px, 100vw"{{ end }}
  srcset='
  {{ if ge $src.Width "500" }}
    {{ with $tiny.RelPermalink }}{{.}} 500w{{ end }}
  {{ end }}
  {{ if ge $src.Width "800" }}
    {{ with $small.RelPermalink }}, {{.}} 800w{{ end }}
  {{ end }}
  {{ if ge $src.Width "1200" }}
    {{ with $medium.RelPermalink }}, {{.}} 1200w{{ end }}
  {{ end }}
  {{ if ge $src.Width "1500" }}
    {{ with $large.RelPermalink }}, {{.}} 1500w {{ end }}
  {{ end }}'
  {{ if .Get (print $medium) }}
    src="{{ $medium.RelPermalink }}"
  {{ else }}
    src="{{ $src.RelPermalink }}"
  {{ end }}
  {{ with .Get "alt" }}alt="{{.}}"{{ else }}alt=""{{ end }}>

Let’s dig deeper into this code to see what’s going on.

Image Processing

Hugo has some great built-in image processing functions. Things like resize, fill, fit, filter, and extract EXIF data. These work only if you call them using a page resource, hence the content organization setup I posted about above.

Typically if you wanted to resize an image you’d have to call it as {{ $image := $resource.Resize "600x" }} in Hugo. So you would be able to call this resource from your markdown post using a shortcode. Since the $image part is a global resource, you can call it anywhere you like.

What this part of the code does is get the name of the img with it’s extension as the $src variable declaration, so when you have img src='name.jpg' you parse the .jpg part and then resize it into 4 different sizes (500px, 800px, 1200px, and 1500px).

{{/* get file that matches the filename as specified as src="" in shortcode */}}
{{ $src := .Page.Resources.GetMatch (printf "*%s*" (.Get "src")) }}

{{/* set image sizes, these are hardcoded for now, x dictates that images are resized to this width */}}

{{ $tinyw := default "500x" }}
{{ $smallw := default "800x" }}
{{ $mediumw := default "1200x" }}
{{ $largew := default "1500x" }}

{{/* resize the src image to the given sizes */}}

{{ .Scratch.Set "tiny" ($src.Resize $tinyw) }}
{{ .Scratch.Set "small" ($src.Resize $smallw) }}
{{ .Scratch.Set "medium" ($src.Resize $mediumw) }}
{{ .Scratch.Set "large" ($src.Resize $largew) }}

{{/* add the processed images to the scratch */}}

{{ $tiny := .Scratch.Get "tiny" }}
{{ $small := .Scratch.Get "small" }}
{{ $medium := .Scratch.Get "medium" }}
{{ $large := .Scratch.Get "large" }}

Since I’m just learning Go Lang and still bumbling my way through Hugo, what I gather from the above code is this. We create four variables of type ‘default’ called $tinyw, $smallw, $mediumw, and $largew. These then get passed into the $src.Resize function that’s built into Hugo to resize them into the 4 new sizes. These new sizes with filenames get assigned to the $tiny, $small, $medium, and $large variables.

After that, it’s a matter of substitution into HTML, which is called out into the remaining code.

<img loading = "lazy"
  {{ with .Get "sizes" }}sizes='{{.}}'{{ else }}sizes="(min-width: 35em) 1200px, 100vw"{{ end }}
  srcset='
  {{ if ge $src.Width "500" }}
    {{ with $tiny.RelPermalink }}{{.}} 500w{{ end }}
  {{ end }}
  {{ if ge $src.Width "800" }}
    {{ with $small.RelPermalink }}, {{.}} 800w{{ end }}
  {{ end }}
  {{ if ge $src.Width "1200" }}
    {{ with $medium.RelPermalink }}, {{.}} 1200w{{ end }}
  {{ end }}
  {{ if ge $src.Width "1500" }}
    {{ with $large.RelPermalink }}, {{.}} 1500w {{ end }}
  {{ end }}'
  {{ if .Get (print $medium) }}
    src="{{ $medium.RelPermalink }}"
  {{ else }}
    src="{{ $src.RelPermalink }}"
  {{ end }}
  {{ with .Get "alt" }}alt="{{.}}"{{ else }}alt=""{{ end }}>

There’s some cool stuff happening here especially with the .RelPermalink tag. Because of my new content folder structure, Hugo knows how to assign a relative permalink to these images in the folder. It just handles it all upon the final build. This sounds pretty mundane but I find it sweet.

Lazy Loading of Images

I still have more work here, especially around lazy loading images but that’ll use javascript and I hate working with JS. Such is life.

It turns out there is a loading attribute you can use called loading = "lazy" that works in modern browsers automatically. So I added this into the above code. Thanks James Loh!

SEO Tip 7 - How to Increase Your Ranking Keywords

I wanted to share this tip with readers who are interested in increasing their ranking keywords with very little effort. I was able to nearly double my ranking keywords in as a little as two weeks by doing a few optimization tweaks.

It all started with some FB messaging to my niece and nephew over in Berlin. Covid19 had them pretty much shut-in for a while so they started working and optimizing their travel blog. Over that time they’ve turned into quite the SEO mavens and we started to share tips between us. Along the way, I learned a lot on how to tweak my blog so that it would start working (aka rank) better in the Google Search Engine.

My initial amount of ranking keywords was only 31. 31?!?! That was LESS than my decent/ok domain authority of 32!

Ranking Keywords Start

How is it even possible that after 13 years of blogging that my ranking keywords were a measly 31? This had me bothered for days until I realized the problem. The real culprit was ME and my ADHD of NOT sticking with a blogging platform. I had killed YEARS worth of built-in SEO by constantly porting my blog between different blogging platforms. Inbound links, comments, and canonical links were all shredded by different permalink structures, non-optimized SEO templates, and high-performance overheads.

So lesson 1, find one CMS platform and stick with it. If you choose to migrate at a later date you’ll have to think beyond doing global 301 redirects, you have to think about how to pull all your SEO parts from around the web to the new platform.

80% Content and 20% Optimization

My number 1 tip is to write great content. That’s it. If you did that on a modern CMS platform with a decent template, then you’ve given yourself a good shot at ranking in Google. I consider great content to be 80% of the SEO battle. Your great content is what will give you long term ‘sticking’ power and is also known as evergreen content.

The Internet is littered with so much crap that tries to rank quickly in Google or other search engines. All that noise is such a futile endeavor IMHO and I stay away from that. I work hard to write relevant, useful, and great evergreen content that my readers can use and learn from. It’s no secret that the most popular section on my blog is my tutorials section. I’ve helped 100’s maybe even 1000’s of readers over the years and that’s what continues to drive me today.

The last 20% is optimization. I’m not interested in catching every possible incremental SEO tweak but just the big ones. Like I wrote above, I’d rather write content BUT there are some important things I needed to do here from a performance standpoint that’s starting to pay off.

My analytics are now showing that organic searches are now the #1 driver of traffic to my site. Before I implemented these tweaks, it was referrals and directs. While that’s nice and all (thank you), new traffic needs to come from organic searches. That’s what you want.

JSON-LD

One thing I implemented in my templates (or at least checked if it was there) was JSON for Linking Data. This is a great way to strip down the metadata from Google and other search engines to quickly parse what the page is about. Mine looks like this:

<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "NewsArticle",
   "sameAs" : [
   "https://twitter.com/neuralmarket",
   "https://www.facebook.com/neuralmarket",
   "https://github.com/tomott12345",
   "https://www.linkedin.com/in/thomasott/"
   ],
  "mainEntityOfPage": {
    "@type": "Blog Post",
    "@id": "{{ .Permalink }}",
    "headline": "{{ substr (replace (.Content) "\n" "") 0 110 }}",
    "author": "{{ .Site.Params.author }}",
    "publisher": {
      "@type": "Organization",
      "name": "{{ .Site.Params.author }}",
      "logo": "{{ .Site.BaseURL }}{{ .Site.Params.logo }}",
    },
    "image": "{{ .Site.BaseURL }}{{ .Params.image }}",
    "datePublished": "{{ .Date.Format "2006-01-02" }}"
  },
  "headline": "{{ substr (replace (.Content) "\n" "") 0 110 }}",
  "alternativeHeadline": "{{ .Site.Title }}",
  "datePublished": "{{ .Date.Format "2006-01-02" }}",
  "dateModified": "{{ .Lastmod.Format "2006-01-02" }}",
  "url": "{{ .Permalink }}",
  "wordCount": "{{ .WordCount }}",
  "author": {
    "@type": "Person",
    "name": "{{ .Site.Params.author }}"
  },
  "publisher": {
    "@type": "Organization",
    "name": "{{ .Site.Params.author }}"
  },
  "image": "",
  {{if .Params.categories }}"genre": "{{ range .Params.categories }}{{ . }}{{ end }}",{{ end }}
  {{if .Params.tags }}"keywords": "{{ range .Params.tags }}{{ . }},{{ end }}",{{ end }}
  "description": "{{ .Description }}"
}
</script>

What this bit of code does is identify the author, images, headlines, word count, keywords, tags, and so much other metadata that identifies your post. That’s the first step in getting keyword rank, being discoverable, and easy for the search engine to identify and index you.

The above code is part of a partial template that I inserted into my post template. Since I use Hugo, a static website generator, what happens is that when I build my site, all this information from my post gets automatically generated into HTML code.

JSON LD shot of my HTML code

This was one of the little tweaks I made that appears to be paying off now.

Granted, I didn’t create the above code and I have to thank Laura’s great work here. I just used her shortcode template and now my images automatically get resizeds during build time. Just this small thing has turbocharged my website performance from an F to an A/A+ on GTMedia and from 40/100 to 90+/100 on Web.dev. Coupled with a CDN, this brought my website out of dregs of a Google Search.

Aliases

Switching CMSs over and over again killed all my inbound links. I started noticing 1000’s of failed referrers and errors in my log files. I solved a lot of these issues by writing 301 redirects in my .htaccess file but I still lost a lot of great inbound links.

This problem stumped me for a while. I didn’t want to go ahead and edit a .htaccess file for every possible permalink permutation (aka sin of the past) when I went from CMS to CMS, so I had to think of a nicer way. I wanted to take a canonical approach by actually coding in any redirects right into my post HTML code.

But how? That sounded like a huge pain in the butt. Thankfully Hugo has a great solution to this, Aliases.

Aliases are a simple way for you to set different up redirects for your blog post. Instead of coding them into the .htaccess file, you just do it in the YAML front-matter section of the post. Then when the post gets built for production, it sets up the canonical links right in the header of the HTML file.

For example, my RapidMinter Tutorials have always been a crowd please, starting with my inaugural posts back in 2007. When I first started on WordPress the permalinks I used were something like this:

/year/month/day/post-title

when I migrated to a more friendly blog URL, I switched it to:

/post-title

Then when I moved to other CMSs, they ranged from all many variants like:

/post-title.html

Or

/blog/entry/post-title

And now:

/tutorials/post-title/index.html

I handled the /blog/entry/post-title problem by doing a redirect to /post-title/ which got me 50% of the way there. I handled the remaining 50% by using Aliases like this:

---
Title: Post Title
Slug: post-title  << this builds to /content-directory-post-is-in/post-title/index.html
Aliases:
- /post-title  << this catches traffic for neuralmarkettrends.com/post-title
- /post-title.html   << this catches traffic for neuralmarkettrends.com/post-title.html

---

This works like a charm and lets me reclaim old linking traffic, but there’s another HUGE untapped benefit here.

By using Aliases, you modify your old permalink with new and improved keywords in the permalink! This is a great way to review your old post and try new keywords to rank for, all by updating your post with the old permalink as aliases and then a brand new ‘slug’ with better keywords that highlight your posts.

Here’s an example of what I’m testing out.

I wrote a great RapidMiner tutorials in 2007 that were titled “Building an AI financial market model - Lesson I.” There was Lesson II, III, and IV. My original permalinks were formed this way building-an-ai-financial-market-model-lesson-i

This is terrible because 1) they’re too long and 2) they don’t make sense to what I want to rank for. I want to rank for ‘RapidMiner’, ‘Model’, ‘AI’, and ‘Finance’. Why? Because I know that a lot of people search for these words.

So I put modified the Slug and create Aliases in the form of this:

Slug: rapidminer-ai-finance-model-1
Aliases:
- /building-an-ai-financial-market-model-lesson-i/
- /building-an-ai-financial-market-model-lesson-i.html
- /tutorials/building-an-ai-financial-market-model-lesson-i/

Google now knows to associate ‘RapidMiner’, ‘Model’, ‘AI’, and ‘Finance’ keywords with my posts, and the old permalink slugs are associated with that.

I will be working on Aliases for the next foreseeable future here as I change my permalinks to be within the max 75 character length AND keyword dense.

Update Old Content

This is something I neglected for a LONG time. I have over 1,000 blog posts here. Some long and some really short and really short posts work against you for SEO as well. So what to do?

I like to write about passive and active investing and I have A LOT of old posts on stocks, mutual funds, ETF’s etc. I plan on doing a ‘where are they now’ update as I stumble across old posts and hopefully point out my investing follies. 13 years of posts is a lot of content alright but what good is it if you can’t revisit it and review what you did wrong or right? Isn’t that what these so-called ‘blogs’ are about? If you can’t learn from your past, how can you navigate your future?

Take the time to refresh your old content with new information, no matter how small or inconsequential it might be. These updates tell the search engines that your blog remains active and will help you with your ranking.

Double the Ranked Keywords

Using the JSON-LD partial and Aliases have made a huge impact in my ranking. The boost to performance from responsive images is a big help too and as I work through updating older content I think I’ll eventually triple or quadruple my ranking keywords. Just these small tweaks helped my double my ranking keywords (as measured by Moz) in 2 weeks. Nice. I can’t wait to see what happens at the end of the Summer here.

Ranking Keywords Start

I messed this up bad with Hugo in the beginning. All my old content was always neuralmarkettrends.com/some-url and when I did the default install with this theme, I placed my content in folders. So all the new permalinks ended up being neuralmarkettrends.com/blog/some-url or neuralmarkettrends.com/tutorials/some-url.

While this OK, it messed up all my backlinks and SEO juice. I spent weeks fixing aliases in my blog posts so that neuralmarkettrends.com/some-url would correctly resolve to neuralmarkettrends.com/tutorials/some-url.

All that hard work was for nothing when I realized I could’ve just added this in the config.toml file:

[permalinks]
  blog = "/:slug/"
  tutorials = "/:slug/"

Save yourself the time and set this up right the first time.

Using Hugo Extended with AWS Amplify

I switched themes for my thomasott.io site and it started to break the AWS Amplify automation. What had happed was the new theme used PostCSS and AWS Amplify’s build settings didn’t reflect the right steps to take to make it work.

The first error I was getting was this: Error building site:POSTCSS: failed to transform "css/main.css" (text/css): resource "sass/sass/style.sass_b1195a7e151e6f4379f823b1b2b4f87a" not found in file cache

The ability to bundle or compile this particular sass/sass/style.sass_b1195a7e151e6f4379f823b1b2b4f87a resource is not available in the generic Hugo software, instead it’s in the Hugo Extended version. It turns out that when you test your website on a Mac using Hugo install via Brew, you get the Hugo Extended version. The only problem is that that AWS Amplify pulls down the generic Hugo release.

So what did I do? Well I spent a lot of time searching the Interwebz for a solution and there were some just said, ‘install the extended version.’ My question is how to do it on AWS Amplify?

My first thought was to just download the extended version of Hugo via a wget and then install it as part of the build process. Little did I know that was only PART of the process.

I ran into difficulty because to use Sass with this theme, I needed to install a few javascript libraries using the NPM manager.

So for anyone that gets stuck on this using Hugo and AWS Amplify, this is what my final build settings looked like:

version: 1
frontend:
  phases:
    build:
      commands:
        - 'npm i -g postcss postcss-cli autoprefixer@9.8.6'
        - 'wget https://github.com/gohugoio/hugo/releases/download/v0.78.2/hugo_extended_0.78.2_Linux-64bit.tar.gz'
        - 'tar -xf hugo_extended_0.78.2_Linux-64bit.tar.gz'
        - 'mv hugo /usr/bin/hugo'
        - 'rm -rf hugo_extended_0.78.2_Linux-64bit.tar.gz'
        - 'hugo version'
        - 'hugo'
  artifacts:
    baseDirectory: public
    files:
      - '**/*'
  cache:
    paths: []

I needed to install postcss, postcss-cli, and autoprefixer version 9.8.6 first and then get the hugo_extended_0.78.2_Linux-64bit.tar.gz release.

Now it works like a charm.

End Notes

I hope you found this post useful. Hugo is very powerful and I find it to be leaps and bounds better than the standard database-driven CMS like Wordpress with it’s 1,000’s of confusing plugins. Still, given enough easy to understand tutorials, I believe Hugo can rise to the top 5 CMS’s quite quickly.