Right, let’s talk about the single most important, and frankly, most annoying, part of using Tailwind with Hugo: purging. You’ve fallen in love with Tailwind’s utility-first speed, but if you just ship the entire 4MB+ of the development build to your users, you’re a monster. A well-intentioned monster, but a monster nonetheless. The purge option in Tailwind is our salvation, our robot vacuum that goes around and only picks up the classes we’ve actually used. But you have to tell it where to look, and with Hugo, that’s a bit of a dance.

The core concept is simple: you provide an array of file paths (so-called content paths) in your tailwind.config.js. At build time, Tailwind’s PurgeCSS engine will rip through every file in that list, find every class it recognizes, and then evict every other class from the final CSS. The magic, and the pain, is in crafting that perfect list of paths.

The Anatomy of Your Hugo Purge Configuration

Your tailwind.config.js doesn’t live in your theme folder; it lives at the root of your Hugo project. This is crucial. Your purge paths need to be relative to this file. Here’s the starter pack that gets you 90% of the way there. It looks a bit like overkill, but trust me, it’s necessary.

// tailwind.config.js
module.exports = {
  purge: {
    content: [
      './layouts/**/*.html',
      './content/**/*.md',
      './content/**/*.html',
      './assets/**/*.js',
      // If you use a theme, you'll need these too:
      // './themes/my-theme/layouts/**/*.html',
      // './themes/my-theme/content/**/*.md',
    ],
  },
  // ... your theme, variants, etc. follow below
}

Let’s break down the “why” for each line:

  • './layouts/**/*.html': This is non-negotiable. This is where all your HTML templates live. Tailwind must scan these to find classes in your base templates, partials, and shortcodes.
  • './content/**/*.md' and './content/**/*.html': Your actual content! Markdown files get rendered into HTML, and Hugo doesn’t pre-compile them for PurgeCSS to see. You have to point Tailwind directly at the raw content files so it can find classes you’ve written in your content body. Yes, this means if you write class="text-blue-500" in a blog post, Tailwind will find it.
  • './assets/**/*.js': Any interactivity. A dropdown menu that toggles hidden? A button that adds a bg-red-500 class on click? Those classes are often constructed in JavaScript. If you don’t include your JS files, those dynamic classes will be purged, and your functionality will break spectacularly in production. It’s a silent, frustrating failure.

The Theme Gotcha and Other Edge Cases

Here’s where they “getcha.” If you’re using a theme that isn’t part of your main repository (i.e., you’re using it as a Hugo module or a git submodule), the paths above will not scan the theme’s files. The ./themes/ directory is a sibling to your ./layouts/, not a child. You have to explicitly add them:

// tailwind.config.js - For a theme-based site
module.exports = {
  purge: {
    content: [
      './layouts/**/*.html',
      './content/**/*.{md,html}',
      './assets/**/*.js',
      // Critical addition for themes:
      './themes/my-theme-name/layouts/**/*.html',
      './themes/my-theme-name/content/**/*.{md,html}',
    ],
  },
}

Another delightful edge case: shortcodes that return HTML. If your shortcode is a simple template in /layouts/shortcodes/, it’s already covered by ./layouts/**/*.html. But if your shortcode is an inline function that builds HTML as a string (e.g., return "<div class='bg-blue-100 p-4'>..."), that string is not in a static file for PurgeCSS to find. You’re hosed. The solution is either to avoid building HTML strings in shortcodes (use a template file) or to use Tailwind’s safelist option to manually whitelist those specific classes.

Best Practices: Don’t Get Caught with Your Styles Down

  1. Test in Production Mode: Hugo’s default server command runs in development mode. The purging only happens when NODE_ENV is set to production. Test your final site with hugo --environment production or HUGO_ENVIRONMENT=production hugo. A missing class will only reveal itself here.
  2. Be Specific with Paths: Using broad globs is fine, but be mindful. Adding ./**/*.html might accidentally suck in node_modules or .cache directories, slowing the purge process to a crawl for no benefit.
  3. The Safelist is Your Friend: Sometimes you genuinely need a class that isn’t explicitly written in your files. Maybe it’s added by a third-party script, or it’s for a dynamic color that comes from a CMS. Tailwind’s safelist option is for this. You can whitelist patterns:
// tailwind.config.js
module.exports = {
  purge: {
    content: [ ... ],
    // These classes will NEVER be purged, even if unused.
    safelist: [
      'bg-red-500',
      'text-center',
      /^bg-/ // <- Whitelist every 'bg-' class. Use cautiously!
    ]
  },
}

Use regex in the safelist with extreme prejudice. Whitelisting every bg- class is a great way to bloat your CSS file right back up again. Be surgical.

Getting the purge right feels like a dark art initially, but it’s just meticulousness. The payoff is a CSS file that’s often under 10KB, which is the whole point of using Tailwind in the first place. It’s worth the fight.