Skip to main content

Building Crazy Fast Websites with Hugo and Webpack - Part 1



Comments: counting...

In this segment you will learn the basics of Hugo, and why I chose it over other static site generators.

What is Hugo

Hugo is a static site generator, it takes in content in the form of markdown and creates HTML pages based on templates you provide.

Hugo was built on the Go programming language, if you are not familiar with Go I would highly recommend reading this great series from The New Dynamic on working with data in Hugo, which will give you some great starting knowledge.

Why Hugo

To be honest, I made several attempts at utilizing Hugo before I started to appreciate it, I suppose I could sum it up by saying “no pain, no gain”.

After spending so much time in Gatsby, NextJS, and the like, Hugo felt a little primitive early on, there is simply less “magic” in how things work. The bright side of that however, is that you are in complete control.

Here are a few reasons why I switched to Hugo:

  • React is great, but overkill for a blog site, I want to have the React option, but not be locked into it on every page.
  • GraphQL is also great, but also overkill for a blog.
  • CSS in JS is awesome for developer experience, but not so much for browser perfomance (although progress is being made on that front).
  • Gatsby builds can take a long time, you can opt to pay for their hosting service that caches optimized images, but Hugo can do image optimization too, and you can set up resource caching on Netlify for free.
  • NextJS can reduce build time by rendering pages server side sort of on demand, but you need a NodeJS server to host the site.
  • Gatsby and NextJS produce pretty fast websites out of the box and that’s pretty much that, but Hugo can be configured to produce even faster websites with a little bit of effort.
  • You don’t need React to create re-usable components, Hugo provides partials and shortcodes that do the same thing without React.

Still not convinced? Check this out, click the button below to show a toast notification.

Here’s how easy it was to make that button work, without any React at all (but if I wanted to, I could easily mount a React component in this article, or several even).

In the markdown file for this article I’m calling a shortcode, which is similar to how MDX can use React components.

Here is the shortcode file contents.

And here is the JS file for this article.

Sure, my create-toast.js file is a little complicated, but I use that all over the site.

Let’s Get Started

For today, we’ll get familiar with Hugo and set up a project. I’ve set up a GitHub repository that you can follow along with if you’d like, I’ll keep a separate branch for each segment in the series.

As always, I’ll also refer you to the official documentation to peruse at your leisure.

NPM Setup

Hugo doesn’t need NPM, but since we’ll be using webpack and other packages we’ll set it up with NPM, and there are NPM packages for Hugo as well to keep it consistent.

hugo-extended, as opposed to hugo, has extra capabilities such as WebP images and Sass support.

We’ll use NPM scripts for running Hugo as well.

We will also need a .gitignore file:


Hugo utilizes a specific directory structure, let’s go over that real quick now.

So, let’s make all of these directories.


I won’t cover all the options here (there are a lot), but this should get you off to a good start.


Instead of copying your frontmatter from another file when creating a new one, you can define it in an archetype (markdown file). You can also use Hugo functions here to fill in dates/etc.

With that file in place, you can now run npx hugo-extended new content/blog/, and Hugo will create your markdown file with the frontmatter in it.

Let’s add some content to that post and categories/tags, note that since this post is a draft, it will show up in development mode, but not when you build the site.

Note that you can add any frontmatter fields you want, even if they aren’t in the archetype (although you should add them so you can remember them easily).


Not just for blog posts, any page on the site must have a markdown file (there is an open issue to allow creating pages from data as well).

You can create subdirectories here for nested pages, and the folders/filenames will make up the URL path. If you want an index page on a path with children you should name it, the underscore lets Hugo know that there are other pages deeper in the filetree.

Let’s make a few pages.


Here we can put any data we may want to be able to work with programmatically, for example let’s add a file for controlling site navigation.

Later on we will utilize this data in the site header.


Layouts is for all of your HTML, be it a template, partial, or shortcode, it lives here.

We will go over these in more detail later on, I also recommend reading more about Hugo’s template lookup order .


I won’t be going into themes, in summary you can set up most of the root directories in a theme directory and set the theme in your config.yml file. It’s a nice feature, but I think it adds some complexity and is beyond the scope of this series (let me know if you disagree, maybe I’ll do a segment on it down the line).

Default Layouts

These templates will be used if no other, more specific templates are found for page or page type. You can also specify a template in your markdown frontmatter (template: template_name), Hugo will search for that template in the layouts/_default directory by filename.

Base Template

Generic Page Template

Home Page Template

We’ll use the same one as generic page for now.

At this point, you should be able to start Hugo (npm start) and see your home page. Try going to the /blog page and you’ll find that Hugo gives you a warning in the console that you are missing a template.

List Template

Now we need a page to list out our posts.

Single Template

Again, we will re-use the default page template here to keep it simple, but you can customize each template as needed.

Now you can navigate to /blog, and click on the link to your post where this template is used.

Terms Template

Here we will list out terms, in our case categories and tags, but you can add your own in the config.yml file.

Now you should be able to visit /categories and /tags and see the page render correctly.

Alternate Layouts

We don’t need any right now, but what if you wanted the “categories” page to look different than the “tags” page?

Easy, just add a new template file at layouts/categories/terms.html and fill it with your new HTML. The same can be done for sections like blog, etc.


Partials are HTML files that can be re-used in any HTML template.

Let’s add a header with some navigation, we’ll use the data from nav.yml to populate it.

Now we just need to add this header partial to our baseof template so it will show on every page.

The period in this line is important because we are passing the current Go context to the partial, this way we could access any global variables or .Scratch from within a partial.


Shortcodes are just like partials, except that partials can only be used in HTML templates, whereas shortcodes can only be used in markdown content.

Let’s make a simple introduction shortcode and use it in our first post (or any markdown page, even the homepage if you wanted to).

Now let’s use that in a markdown file.

It’s worth noting that the internal text (.Inner) is not treated as markdown, so you will get plain text. You can use {{ .Inner | markdownify }} in your shortcode to convert the internal text to markdown, which will then be automatically rendered into HTML. You can also omit the internal text and closing tag entirely if you don’t use .Inner in the shortcode file.


The standard way to add CSS and JS to a Hugo site is pretty simple, and we’ll be setting up Webpack instead later on, but for now let’s add a few basic styles.

First, we will create a global CSS file in the assets directory (Nothing fancy here, it’s just temporary anyway).

Now we just grab that file in the baseof template.

Image Manipulation

Let’s set up some responsive lazy loaded image action, we’re going to use some more advanced Hugo functions but don’t panic if it is confusing, it will start to make sense as you learn more about Hugo, feel free to just copy this file over without trying to parse it for now.

Now, how can we use this partial? You can pass variables to partials in a map. Here’s a shortcode to demonstrate what that looks like.

At this point, you can use it in a blog post. Let’s add a second post and use the responsive image shortcode, which then uses the responsive image partial to render out an image.

Note that this time we are calling the file and putting it in a folder called second-post, this way we can add another folder for images.

Don’t forget to add an image at content/blog/second/post/img/photo.jpg, you can change the filename or the name of the img folder, just make sure the shortcode filename value matches that too.

At this point, you should see a very blurry pixelated image. We need some JavaScript to swap out the low resolution default image to the full quality placeholder image. This way the page will load fast, and the full size images will only be loaded if a visitor scrolls down the page, saving on bandwidth.


Let’s create a script to handle our lazy loaded images.

All we need to do now is pull that script into the baseof template.

You should now see the full quality image on your page, huzzah!

Again, you can use some Hugo functions to manipulate your scripts, you can even use esbuild to bundle and minify scripts. But we will be setting up Webpack in the next segment which will be a lot more versatile in the long run.


At this point, we have set up a basic Hugo project using NPM and added some content, partials, shortcodes, JavaScript, and CSS. Hopefully you’ve learned a bit about Hugo and will check out the documentation to see what else it can do!

In the next segment, we will set up Webpack and integrate it into our Hugo project, make sure you so you don’t miss it!