Rebuilding this Site with Hugo and Tailwindcss

I build almost all of my personal sites as pure vanilla web 1.0 HTML/CSS/JS—no frameworks and as few 3rd party scripts as possible. It’s a delightfully fast workflow. Two decades ago, the ability to just throw an index.html up onto an Apache server and see it suddenly live on the web felt like magic, and the experience hasn’t lost its charm. (That said, create-react-app actually does a pretty good job of capturing that same feeling.)

This site has spent the last few years as a scattered pile of bespoke HTML and CSS files, which was great for just sticking a site up on the web ASAP, but has ended up a more-than-negligible barrier to me posting anything I’ve written. In short, the very same limitations that motivated web 2.0 have pushed me to abandon the old ways.

Why I went with a static site generator and how I picked Hugo

My primary concerns when deciding how I should rebuild this site were that it should be fun, result in a workflow that would encourage me to write and publish more, and ensure top-notch performance and accessibility. The fun part meant using something new. I’ve built my fair share of custom Wordpress themes and React apps in my career, and even though I no longer do either professionally, I still wanted some novelty. That meant both of those were out of the running.

I’ve had my eye on static site generators (SSGs) since they started gaining traction. In general, I think the web is way slower and more bloated than it needs to be. Some sites truly do require dynamic server-side processing, but there are many that do not. Blogs used to be too painful to create without some kind of CMS, but SSGs are rapidly changing that.

Being a static site is not a virtue in and of itself. You can still shoot yourself in the foot by filling it with heavy Javascript or massive unoptimized images. Nevertheless, it makes things easier, plus a lot of security threats simply become inapplicable. (That’s not to say you can’t have vulnerabilities in static sites).

To put it simply:

Tailwindcss - my foray into atomic CSS frameworks

For the uninitiated, atomic CSS takes the idea that styles should be composed from descriptive single-utility classes. This gives you markup that looks like <div class="pr-6 pl-6 bg-gray-100 rounded mt-4 mb-4">. If you’re anything like me, the preceding bit of code gives you an uneasy, disgusted feeling. This is in deep opposition to one of the other leading style philosophies, semantic CSS. Optimally semantic CSS uses class names to describe the content, not how it’s to be styled. Function, not form.

My sensibilities lean pretty deeply toward the semantic approach. I was unsettled the first time I saw atomic-style CSS in action. Over the last year, I’ve begrudgingly accepted that some of these principles might actually make a lot of sense for integrating CSS within frontend JS frameworks like React or Vue. I also had to acknowledge that although I had a gut-level aversion to atomic CSS, I’d never actually used it, nor did I have any objections that weren’t purely reactionary. To make things worse, some developers I respect started talking positively about Tailwindcss.

In short, I wanted to earn the right to hate atomic CSS.

Setting up Hugo + Tailwindcss

Installation and set up were a breeze. I used a tutorial by Div Rhino to get Hugo wired up to Tailwindcss and its dependencies. Hugo has a fast, live-reloading dev server built in with 0 configuration necessary, which absolutely buoyed my mood while I was hacking this site together.

The Hugo documentation, which had looked so thorough and appealing, turned out to be a minor disappointment. It’s not bad, but it’s not amazing. It took reading through several community forums to figure out how to grab all posts from a specific type, so I could put my most recent 3 blog posts on the homepage. The solution was quite simple, but the documentation lacks good examples and recipes, and doesn’t really make it clear what fields exist in the different site and page variables.

The Go template language used in Hugo was perfectly fine, but its best feature is the use of pipes (akin to Unix pipes) for passing and transforming outputs. It is an intuitive way to handle things like applying transformations to CSS. For example, here’s how this site handles the main CSS in the head template:

{{ $styles := resources.Get "css/styles.scss" | toCSS | postCSS (dict "config" "./assets/css/postcss.config.js") }}
{{ if .Site.IsServer }}
    <link rel="stylesheet" href="{{ $styles.RelPermalink }}">
{{ else }}
    {{ $styles := $styles | minify | fingerprint }}
    <link rel="stylesheet" href="{{ $styles.Permalink }}" integrity="{{ $styles.Data.Integrity }}">
{{ end }}

Building the theme

As you can probably tell from the site you are looking at right now, I stuck with an incredibly simple structure. There are just 4 different layout templates, and each template is a miniscule amount of markup. It still took me a few days to put the site together, but I’m going to chalk that up to the fact that I was learning two new frameworks, and not to anything inherently slow about Hugo or Tailwindcss.

I did not dislike Tailwindcss nearly as much as I expected I would. Well, that’s not quite accurate. For about the first hour I was using it, I was really really angry. There was a lot of muttering. Then, I installed a Tailwindcss intellisense plugin for VS Code. That helped dramatically, because most of the utility classes have names similar to their style attribute name, so I could just kind of guess my way to the styles I wanted.

It was fun to not have to leave the HTML as I tweaked styles and layouts. A few of the utility classes were particularly nice - the divider and spacer classes do exactly what you want them to do. It’s not that it’s hard to deal with the problem of spacing child elements evenly, but it’s just nice to have someone else do it, honestly.

This site only uses 11 custom base style rules, and those are composed entirely from applied Tailwindcss classes. I just could not bring myself to add class="text-cyan-700 underline hover:text-pink-700 focus:text-pink-700" to every single link. Do people actually do that? I hope not. It feels dirty and wrong and if this is the future, I will stay in the past thankyouverymuch.

I’m not sure if I’d use Tailwindcss or a similar CSS framework again in the future. This was a pretty tiny test project, but I’m interested to see how I feel about it in a year or two, when I’ve had to make tweaks and changes and updates. Maintainability is always the big question with CSS in my experience.

Final thoughts

Migrating the site to Hugo has definitely reduced the barrier to writing new posts, although I still have a few more improvements before it’s totally seamless. I’m still deciding whether I’ll write a bash script or a GitHub action to handle deploying updates to my hosting server. I know I could go with Netlify, but I’d rather just roll my own solution.

I wouldn’t recommend this workflow for a client site, at least, not without some kind of headless CMS added to the stack. However, it’s really pleasant for anyone who already feels comfortable with Markdown, the command line, and using YAML to create metadata. I haven’t deployed the site live yet, so stay tuned for performance and SEO updates.

Edit

The tutorial I mentioned above for getting the site set up with Hugo + Tailwindcss worked great in development, but it does not include instructions for purging extra classes in your production build. Without purging, Tailwindcss made me a 3MB stylesheet! After the purge, I had a much more sensible 10KB.

  1. Add purge rules to your tailwind.config.js file. Mine looked like this:

    const themeDir = __dirname + '/../../';
    
    module.exports = {
        theme: {
            ...
        },
        variants: {},
        plugins: [],
        purge: {
            content: [
                themeDir + 'layouts/**/*.html',
                './*(layouts|content|data|static)/**/*.*(html|toml|md)'
            ],
          }
    }
  2. Set Node Environment to production

    1
    2
    
    export NODE_ENV=production
    hugo