Hugo, Tailwind & Netlify = <3

So one of the major things that I have an issue with is getting simple websites/projects up and running. Luckily we live in the future. There are services for all sorts of things and many of them are free, open source or extremely low cost. At the end of the day, the only thing holding people back is time.

Why Hugo/Tailwind/Netlify

Hugo

Hugo is a great framework for creating websites from markdown files. What is really awesome is how quick it is and how featureful the templating engine is and it is all based on go. Hugo has a couple awesome features that stand out:

  1. Custom markdown tags
  2. Ability to ingest data from formats like like CSV, JSON, YAML
  3. Flexible templating engine
  4. Its really darn quick

Tailwind

I have been following tailwind for a couple years now and it has always intrigued me. Having watched some of the videos they have produced really cemented that this is something I have always wanted when designing sites. Having something that I can basically mix into my different web layouts is incredibly helpful and to be honest, while using tailwind many of the UI/UX/design things just kind of “clicked”.

One of the biggest problems I have had in the past is being able to take an idea and lay it out on the screen how I see it in my head. Tailwind has helped with this and highly recommend checking it out.

The biggest issue that I have with it and integrating with existing projects is that it utilizes PostCSS fairly heavily, which can be a pain to implement if you aren’t in the node/js ecosystem. However, this has been changing as more projects work with it much more seamlessly. In the case of Hugo, it is builtin as a processor, which is quite nice.

Netlify

Honestly, I have been really impressed by netlify… It takes most of the hassle out of deploying static sites. I have used GitHub pages, which is a great feature, but getting a bit dated in its features.

The nice thing about netlify is how quickly you can get up and running, and it is free for smaller projects.

Setting up the hugo site and directory structure.

Follow the guide from the hugo quickstart and yo will be left with a barebones hugo site.

For my example have created a new site using the hugo new site sheeple.us command. After creating the directory, I initialized a new git repo in that directory, and from here on out it will be what we use for this post.

Installing tailwind

We want to create a new NPM package for our website, so that it can easily be used to generate the content and install dependencies, such as tailwind and any custom tailwind components.

Simply run npm init in your site directory and run through the steps.


jpleger@jpleger-mac [12:09:23] [~/vcs/sheeple.us] [master *]
-> % npm init 
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (sheeple.us) 
version: (1.0.0) 
description: sheeple.us site
entry point: (index.js) 
test command: 
git repository: (https://github.com/jpleger/sheeple.us.git) 
keywords: 
author: 
license: (ISC) 
About to write to /Users/jpleger/vcs/sheeple.us/package.json:

{
  "name": "sheeple.us",
  "version": "1.0.0",
  "description": "sheeple.us site",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/jpleger/sheeple.us.git"
  },
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/jpleger/sheeple.us/issues"
  },
  "homepage": "https://github.com/jpleger/sheeple.us#readme"
}


Is this OK? (yes) yes
jpleger@jpleger-mac [12:12:33] [~/vcs/sheeple.us] [master *]
-> %

At this point, we should be ready to install tailwind using the npm install tailwindcss@latest postcss@latest autoprefixer@latest postcss-cli@latest command. If you look at the package.json, you will now see that tailwindcss has been added as a dependency.

  "dependencies": {
    "autoprefixer": "^10.1.0",
    "postcss": "^8.2.1",
    "postcss-cli": "^8.3.1",
    "tailwindcss": "^2.0.2"
  }

I would also recommend removing the node_modules directory from git using a .gitignore statement.

Helpful scripts for running hugo dev/build

This is a personal preference, but node provides a way of running scripts using npm. These could just as easily be done with a command alias, but one of the advantages of using the node method is that netilify can use this command as well, which makes builds/test server deployments more consistent.

I have 2 commands that I have added to my package.json file, which allow me to run npm run dev to create a dev server and npm run build to build the site.

Creating the hugo theme

Using the hugo new theme sheeple I create a blank theme under the themes directory. At this point I modify my hugo config.[yaml|toml|json] file to reflect the new theme.

My hugo /config.yaml now looks like this:

baseURL: "https://sheeple.us/"
languageCode: "en-us"
title: "sheeple.us"
theme: "sheeple"

This removes any warnings I would have had with a default hugo site install:

WARN 2020/12/29 12:31:24 found no layout file for "HTML" for kind "taxonomy": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN 2020/12/29 12:31:24 found no layout file for "HTML" for kind "home": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
WARN 2020/12/29 12:31:24 found no layout file for "HTML" for kind "taxonomy": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.

Configuring PostCSS

Create a new directory for your CSS files under your newly created theme. In my case I ran mkdir -p themes/sheeple/assets/css/.

PostCSS/tailwind config file

Once you have the directory, create a new postcss.config.js file under the main theme directory. We essentially follow the PostCSS installation instructions from the tailwind documentation. One thing to note, when hugo pulls in postcss it does so in the context of the site, then the theme. Keeping that in mind, we need to ensure that the config is relative to the called file, so we add a config option that tells tailwind where its config is.

// themes/sheeple/postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: { config: __dirname + '/tailwind.config.js' },
    autoprefixer: {},
  }
}

I then used npx tailwindcss init to create a new tailwind config file and moved the tailwind.config.js to the theme directory root. I have also added forced purging to keep the CSS size down.

// themes/sheeple/tailwind.config.js
module.exports = {
  purge: {
    enabled: true,
    content:[
      '**/layouts/**/*.html',
      '**/content/**/*.md',
    ],
  },
  
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

Now that we have the configuration files, we need to include tailwind in our sites css.

/* themes/sheeple/assets/css/site.js */

@tailwind base;
@tailwind components;
@tailwind utilities;

Hugo Pipes + PostCSS

At this point, the CSS files have been configured, but they aren’t doing anything. To get them to actually run through PostCSS, we need to tell Hugo to call the PostCSS via Hugo.

Index:

<!-- themes/sheeple/layouts/index.html -->
<!doctype html>
<html>
{{ partial "head.html" . }}
<body>
    {{- partial "header.html" . }}
    <div id="content">
    This is the content
    </div>
    {{- partial "footer.html" . }}
</body>
</html>

Head:

<!-- themes/sheeple/layouts/partials/head.html -->
<head>
    {{ $css := resources.Get "css/site.css" -}}
    {{- $css = $css | resources.PostCSS -}}
    {{- if hugo.IsProduction -}}
    {{- $css = $css | minify | fingerprint | resources.PostProcess -}}
    {{- end -}}
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link href="{{ $css.RelPermalink }}" rel="stylesheet"{{ if hugo.IsProduction }} integrity="{{ $css.Data.Integrity }}"{{ end }}/>
</head>

Testing

Once you have everything setup, you should be able to run npm run dev and see a very basic page. If you view the css source, it should purge the unused things from templates and have the tailwind info in your CSS file.

Deploying with Netlify

Deployment with netlify is pretty straightforward. If you have a proper build in your NPM, you can actually just use their wizard, without having to setup a netlify.toml file.

Otherwise there are a number of tutorials on how to setup hugo with netlify.

Avatar photo James is a security nerd with a technology problem. Follow him on Twitter TwitterTweet