Hugo and Bootstrap

Setting hugo to use bootstrap in a way that allows for customization is not difficult, but isn’t transparent either.


Bootstrap is a Sass based CSS framework. It has been wrapped into a Node package so that it can be “installed” just like any other node package.

Hugo has a a Sass compiler built into it. So, all we need to do is make the files available to Hugo during rendering.


I will assume you already have a hugo project started.

I will also assume you already know a fair bit about hugo and can, for instance, find where the htnl head tag matter is defined for your site.

So, go cd into the hugo project directory and we’ll start there.

Install bootstrap

We’ll need npm, so lets let it initialize itself. We’re really only using it as a package manager, so the actual answers to the questions that it asks are not important.

# use -y to take all the defaults
npm init -y

Make sure that you update your .gitignore file so that the node_modules directory is not checked in.

echo 'node_modules/' >> .gitignore

Now install the needed modules

npm install bootstrap

Mount Bootstrap files in hugo filesystem

We need to make the bootstrap files - both SCSS and Javascript - available to hugo during the rendering process. But we don’t want to copy since that will make it difficult to update Bootstrap.

Instead, we are going to use Hugo modules to form a union filesystem - leaving the Bootstrap files in their original place in node_modules so that npm can update for us.

Add the following to the hugo.toml file. This will tell hugo to pretend that the bootstrap js file is actually located in the ‘assets/js’ directory.

    source = "assets"
    target = "assets"
    source = "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"
    target = "assets/js/bootstrap.bundle.min.js"

Add Bootstrap Javascript

Add the following to your html near the bottom of the body. For a “normal” hugo site this will probably be in the file layouts/partials/footer.html

If you have other javascript for your site, you can use resources.Concat to bundle it with the bootstrap js. This will make the site load faster since only one minified file will need to be loaded.

<!-- JavaScript libs are placed at the end of the document so the pages load faster -->

{{ $bootstrap := resources.Get "js/bootstrap.bundle.min.js" }}
{{ $js := $bootstrap | fingerprint }}

<script src="{{ $js.Permalink }}" integrity="{{ $js.Data.Integrity }}" defer></script>

Add Sass files and configuration

Look at the Bootstrap Documentation to see what the sass file should look like. For this article, we will use the simpler of the two options.

Note That the Bootstrap documents use paths such as ../node_modules/some_path - you will need to use simply ../some_path instead.

First the “main” sass file that brings in bootstrap.

@import "overrides.scss";

@import "node_modules/bootstrap/scss/bootstrap";

@import "styles.scss";

Then add the two custom files overrides.scss and styles.scss

All three files should be put into assets/sass directory.

Now lets add the css files to header of the site. This will normally be in layouts/partials/head.html or similar.

{{ $options := (dict "targetPath" "css/styles.css" "outputStyle" "compressed") }}
{{ $styles := resources.Get "sass/main.css" }}
{{ $css := toCSS $styles $options | minify | fingerprint }}

<link rel="stylesheet" href="{{ $css.RelPermalink }}" integrity="{{ $css.Data.Integrity }}">


The boostrap css file - even minified - is over 200Kib. Most sites will only use a small fraction of the utility classes defined. It makes sesnse to use the purgeCSS utility to strip out unused classes.

To help with this, hugo can be made to create a file that contains all the classes and tags that it finds in the html it renders.

Install needed modules

npm install --save-dev "@fullhuman/postcss-purgecss" postcss postcss-cli postcss-flexbugs-fixes

Update hugo configuration

The exact change needed in hugo.toml will depend on exactly which version of hugo you have installed. If you are using v115.3.0 or above the you need the build.buildStats lines. If you are using something below that version, you will need the writeStats line.

The block below contains both and it is fine (for now) to include both.

  writeStats = true
    enable = true

create postcss configuration.

Put the following in the postcss.config.js file.

const purgecss = require('@fullhuman/postcss-purgecss')({
  content: [ './hugo_stats.json' ],
  defaultExtractor: (content) => {
      let els = JSON.parse(content).htmlElements;
      return els.tags.concat(els.classes, els.ids);

module.exports = {
  plugins: [

If you only want the purgeCSS to run in production please see the PurgeCSS with Hugo documentation

Update the header

We need to change the template we wrote above in order to force hugo to run the PostCSS process.

We will also use resources.PostProcess to tell hugo to wait to run this bit until very last. We need the other pages to be rendered in order to make sure the hugo_stats.json file contains data for everything.

#replace this:
{{ $css := toCSS $styles $options | minify | fingerprint }}

# with this:
{{ $css := $styles | toCSS $options | postCSS | minify | fingerprint | resources.PostProcess }}


And that is it!

Study the Bootstrap documentation on how to change colors, etc.