Building a Static Website Hugo Tailwind and AlpineJS - Part 2

7 minute read | | Author: Murtaza Nooruddin

This article will help you setup a basic boilerplate project with Hugo Server, a custom theme and tailwindcss 3.3.x loaded. If you are experienced with Hugo, you can use then build your own structure and site, alternatively, you can use the project to then import ready made tailwind templates and adapt those pages to Hugo.

Starting & configuring your project with TailwindCSS, AlpineJS and Hugo Server

Please make sure you have gone through Part 1 for background and pre-reqs.

Download the tools

The tools we will use are cross-platform and should work on Windows, MacOS and Linux. You will need the following tools to be downloaded and installed:

  1. Visual Studio Code OR your favorite code editor for html, css and editing/maintaining the project, vscode is what I will be using throughout the guide
  2. Hugo static site builder - IMPORTANT: make sure you pick the extended version, Hugo_extended_0.xx.x_…
  3. Node & NPM - We use this to maintain project dependencies
  4. Git - This is optional, but highly recommended for version control and remote backups.
  5. A copy of the sample project used in this article is available on GitHub

We will not cover Git basics in this article, but feel free to read up separately as there are plenty of resources.

At the time of writing this article, I am using Hugo Static Site Generator hugo v0.115.1+extended and TailwindCSS 3.3.3. If you get errors or commands look unfamiliar, you can revert to these versions

Initialize project directory and configure Tailwind and postfix

Go to a suitable home/project folder on your computer. Open command prompt/shell to navigate into it:

Hugo needs to be in your environment variables or system path to run, make sure you do that as we will need to run Hugo command very often

hugo new site hugo-tailwind

This will create a Hugo skeleton site within milliseconds. Too good to be true, but Hugo is fast, really fast. You should see directories: layouts, data, content, archetypes, resources…more on this later.

cd hugo-tailwind

Now that our project folder is ready with Hugo’s structure, we initialize it with NPM to download our dependency, just tailwind for now.

npm init

This will initialize project directory and ask some questions, give your project name on the first prompt: package name: hugo-website

version: 1.0.0

description: test website for Hugo and tailwind

entry point:(index.js) index.html

…continue configuring values or just hit enter to have default values on the prompts. These won’t impact the project, it’s just for posterity.

npm install tailwindcss postcss autoprefixer alpinejs
npx tailwindcss init

This will install the current version of dependencies and initialize a tailwind.config.js. At the time of this article it was tailwindcss 3.3.3.

Optional step:

git init

Skip this step, if you don’t want to create a Git repository. If you do run the command above it will init git repo, this lets you checkout your project as git repository and synchronise your project on Github or a cloud repo provider. Details of that are omitted here to keep the article focused on topic at hand. Feel free to google git tutorials if you want to know more.

(Optional: if you initialized git init above) Next, open the project the directory in vscode.

Create a .gitignore file in root directory of the project. For the experienced readers, it’s obvious to ignore node_modules directory from synchronising with git repository. Also public is the output folder from Hugo as we will later see and doesn’t need to sync as you can always generate it from source within milliseconds. The following is the minimum recommended folders you should add for our Hugo project.


Next steps are compulsory:

Modify the tailwind.config.js to include content from hugo_stats.json (more on this shortly)

/** @type {import('tailwindcss').Config} */
module.exports = {
   content: [

  theme: {
    extend: {},
  plugins: [],

Create another file in the root of the project postcss.config.js and paste the following. PostCSS purges unused styles, ensuring the final CSS is lightweight and only includes what’s used in the Hugo templates. Autoprefixer ensures the generated CSS works consistently across different browsers by adding necessary vendor prefixes.

let tailwindConfig = process.env.HUGO_FILE_TAILWIND_CONFIG_JS || './tailwind.config.js';
const tailwind = require('tailwindcss')(tailwindConfig);
const autoprefixer = require('autoprefixer');

module.exports = {
	// eslint-disable-next-line no-process-env
	plugins: [tailwind,  ...(process.env.HUGO_ENVIRONMENT === 'production' ? [autoprefixer] : [])],

Create Hugo Config file

Update your `hugo.toml`` file in the main directoy with the following configuration. It adds a lot of good defaults such as creating sitemaps, forcing a hugo version and busting css cache for postcss and more importantly mounting assets directory for easy inclusion of css and js assets. You will see in head and footer templates later.

baseURL = ''
languageCode = 'en-AU'
title = 'Awesome Tailwind Hugo Site'

  date = ["date", "publishDate", "lastmod"]
  lastmod = ["lastmod", ":fileModTime", ":default"]

  categories = "categories"

  changefreq = 'monthly'
  filename = 'sitemap.xml'

    extended = true
    min      = "0.115.0"
    source = "node_modules/alpinejs/dist"
    target = "assets/alpinejs"
    source = "assets"
    target = "assets"
    source = "hugo_stats.json"
    target = "assets/watching/hugo_stats.json"

  writeStats = true
    source = "assets/watching/hugo_stats\\.json"
    target = "styles\\.css"
    source = "(postcss|tailwind)\\.config\\.js"
    target = "css"
    source = "assets/.*\\.(js|ts|jsx|tsx)"
    target = "js"
    source = "assets/.*\\.(.*)$"
    target = "$1"

Add layout files

Create the following folders


Then create these files


 {{ define "main" }}

<div class="p-10">{{ .Content}}</div>

{{ end }}


<!DOCTYPE html>
<html lang="en" xml:lang="en" xmlns= "" >
    {{- partial "head.html" . -}}
        {{- partial "header.html" . -}}
        <div id="content">
        {{- block "main" . }}{{- end }}
        {{- partial "footer.html" . -}}


      <title>{{ .Site.Title }}</title>
      <meta charset="UTF-8">
      <meta name="google" content="notranslate">
      <meta http-equiv="Content-Language" content="en">
      <meta name="description" content="Site description, this can be imported from hugo site params" >
      <meta meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5">

      {{/* styles */}}
      {{ $options := dict "inlineImports" true }}
      {{ $styles := resources.Get "css/styles.css" }}
      {{ $styles = $styles  | resources.PostCSS $options }}
      {{ if hugo.IsProduction }}
        {{ $styles = $styles | minify | fingerprint | resources.PostProcess }}
      {{ end }}
      <link href="{{ $styles.RelPermalink }}" rel="stylesheet" >

The above is the head tag that includes tailwindcss from assets mounted in hugo.toml and when you deploy the website, it minifies the final stylesheet as well.


<!-- we can build a navigation or common header here -->


    {{ $alpine := resources.Get "alpinejs/cdn.min.js" }}
<script src="{{ $alpine.RelPermalink }}" defer></script>


The boring bit is over. The fun begins, your very first content file

Let’s add a home page, by creating and editing content/_index.html. _index.html is a special file that is considered homepage in hugo and is treated separately to rest of pages. We will add a template for rest of single and list pages later.

# home page, front matter goes here if needed
<section class="text-center">
    <h1 class="text-blue-500 text-4xl">Welcome to my first Hugo Tailwind Website</h1>
    <p> ...and it works!</p>

    <div class="my-10" x-data="{ count: 0 }">
        <h2> Let's check if AlpineJS is working</h2>
        <button type="button" class="bg-black border-2 p-1 text-white" x-on:click="count++">Increment</button>
        <span x-text="count"></span>

Next, edit package.json and add the following start script tag. This is not essential but makes it easier to run your project by npm start and not having to remember some of these annoying parameters which I learnt the hard way are almost essential due to tailwind/postcss and hugo caching problems.

  scripts: {
      "test": "echo \"Error: no test specified\" && exit 1",
      "start": "hugo server --disableFastRender --ignoreCache --noHTTPCache --buildDrafts",

Now, to test if the site is working:

npm start

This launches Hugo development server and you can see your website by opening http://localhost:1313. You will see this on the command window as you type the command above.

Congratulations you have successfully setup a basic Hugo project with tailwind+alpinejs. The counter should increment and the styling should appear as below if all went well:

vscdode project init

If you followed everything successfully so far, you have project ready to start building upon yourself. However, stick with part 3 of this blog and I will show you how to add pages, blogs, taxonomy, top menu, conditional logic within Hugo to build pages associated with menus. Also add google analytics, json/ld schema that can be added dynamically based on page type.

Continue to Part 3

decor decor

Have a great product or start up idea?
Let's connect, no obligations

Book a web meeting decor decor