Using Tailwind CSS with the Zola Static Site Generator

2042 words

After using it in a couple small projects I've fallen in love with the Tailwind CSS framework as a way to structure and apply CSS, and as a design system. When I was looking for a static site generator to build this site with, one of my search criteria was support for Tailwind CSS, either baked-in, or easy to add.

I ultimately ended up adopting the Zola static site generator due to its simplicity, clear documentation, performance (haha millisecond builds go brrr), and it having a template language I preferred over the one used by Hugo. Despite Zola having an inbuilt support for processing SASS/SCSS, it does so with a SASS processor that's written in Rust and included in the Zola binary. As Tailwind CSS is build on top of the PostCSS tool, we can't throw the Tailwind styles into the Zola CSS system and call it a day.

Ways to include Tailwind in a Zola project

There are two possible ways to include Tailwind in a Zola project:

  1. Include the prebuilt CSS file from a CND.
  2. Make a custom build process for the CSS to use alongside Zola.

Prebuilt

Using the prebuilt version of Tailwind is a decent solution if you want to whip up a quick prototype. Add the stylesheet in the page head and start writing classes. The file size of the prebuilt version of Tailwind is large compared to what you can achieve by building your own (and most importantly purging the styles, but more on that later). The difference can be 5-10x after compression (2.5kb to 27kb).

The prebuilt version also lacks some of the more advanced features of the framework that need to be separately enabled, like group-hover and focus-within. You also miss out on using additional plugins to augment and extend Tailwind (e.g. @tailwindcss/custom-forms that makes integrating HTML forms with Tailwind less of a pain).

As such, to cut down on the stylesheet size and give you more power over the available styles, setting up a custom build process is a good option.

Tailwind CSS Build Process

Setup

To build and configure Tailwind yourself, a separate build process needs to be set up. The idea is to build Tailwind and then include the resulting style file like you would use any other static file with Zola.

As stated, Tailwind is built with PostCSS - a tool for generating and modifying CSS with Javascript. If we want to build Tailwind, we need to use PostCSS, which requires a separate build tool to run. I chose Parcel as the build tool as it includes PostCSS.

This article assumes you have already installed Zola and have setup the directory structure with zola init. All the commands are relative to the root folder of the Zola structure.

As PostCSS in included in Parcel, the only two dependencies we need to install are Parcel and Tailwind. To start you need a new npm/yarn project. I'm going to use yarn, but the steps should work the same with npm.

yarn init; # or npm init
yarn add parcel-bundler tailwindcss; # or npm install ...

Note that you might not need to add Parcel to your project if you are gonna build the site on your computer and deploy the static files manually. Instead you can install it globally to use in any project. As I will be using Netlify to build and serve these files, I want Parcel included in the project dependencies.

You can also install Parcel as a devDependency using the --devflag, but my Netlify builds run in production mode where the devDependencies are not installed.

Styles

Next we need to add the base Tailwind style file into our project. I keep the styles that need PostCSS processing separate from the Zola file structure and incorporate only the finished styles in the Zola build process.

Create a folder called styles and add a file styles.css with the base Tailwind styles as the contents:

@tailwind base;

@tailwind components;

@tailwind utilities;

Processing the Styles

Next we need to configure PostCSS to use the Tailwind plugin (and the autoprefixer plugin), so add the following to postcss.config.js.

module.exports = {
  plugins: [
      require("tailwindcss"),
      require("autoprefixer")
  ],
};

Lastly we want to set up two Parcel commands, one to watch our CSS files and rebuild them whenever something changes, and one to build a production version.

One drawback of running a separate build system for the styles is the time building them takes compared to the speed that Zola builds the rest of the site with. Fortunately with Tailwind you are rarely writing new CSS but modifying the template files instead, so the increased time is often not an issue.

To setup the watch script add the following into your package.json

"scripts": {
  "watch": "parcel watch styles/styles.css --out-dir static/styles --no-hmr --no-source-maps"
}

The script will watch the styles.css file we set up earlier, and build new styles to the Zola static files directory. We also disable the hot module reload functionality because Zola will handle that for us, and disable source maps, as our CSS files will often not be complicated.

Now we only need to include the styles in our templates and we're ready to begin styling our site.


We use the Zola get_url shortcode to get the absolute URL of the stylesheet.

Workflow

For a smooth workflow you can launch two tabs of your terminal and start yarn run watch in one and zola serve in the other. When you make any changes to your styles, Parcel will process your styles and output them to the Zola static files directory. Zola will pick up on those changes and recompile your site.

Styling the site is as easy as using the Tailwind classes in your template files. The Tailwind docs have great examples of the how and why of styling with utility classes.

Styling Markdown Content

When Zola processes your content, the markdown is converted into HTML elements. As we can't control the classes of these elements (nor would we want to pollute the markdown files with HTML classes), we need to style the elements directly. However, we don't want to just style say the <p> tag as those styles would then bleed into other elements on our pages. Instead we want to scope all markdown related styles inside a specific selector. This can be done multiple way but I chose to put all my markdown content inside a div with a specific class.

{{page.content | safe}}

This way I can scope all styles under the .markdown-content selector and don't have to worry about the styles bleeding to unexpected elements.

For readability I separated the markdown styles into their own CSS file which can be imported into the main styles.css file.

@tailwind base;

@tailwind components;

@import "./markdown-content.css";

@tailwind utilities;

Note: it's Parcel here that's resolving the @import statements. Normally with PostCSS you would need to use something like postcss-import to handle import statements (and work around some restrictions it comes with), but Parcel happily deals with them for us.

Even though we have to style the markdown content elements in a CSS file, we can still use the Tailwind classes by utilizing the Tailwind @apply directive.

.markdown-content {
  @apply font-serif;
}

.markdown-content p {
  @apply mb-5;
}

.markdown-content a {
  @apply text-pink-500;
}
.markdown-content a:hover {
  @apply text-pink-400 underline;
}
/* rest of the styles */

You can either manually prefix the style declarations with .markdown-content or use one of the PostCSS plugins to provide nesting and other features. Personally I use the postcss-preset-env plugin and write my CSS using the future CSS nesting syntax.

.markdown-content {
  @apply font-serif;

  & p {
    @apply mb-5;
  }

  & a {
    @apply text-pink-500;
    &:hover {
      @apply text-pink-400 underline;
    }
  }
  /* rest of the styles */
}

Building for Production

To build a production version of the stylesheet, we need to add a separate build script. The Parcel build command will create an optimized version of our styles that are better suited for public sites than the development version created by the watch command.

Add a new build script to your package.json. I chose to omit the source maps from the build version too, but there's no need to disable to hot module reload functionality here, as it is not created by the build command anyway.

"scripts": {
    "build": "parcel build styles/styles.css --out-dir static/styles --no-source-maps",
    "watch": "parcel watch styles/styles.css --out-dir static/styles --no-hmr --no-source-maps"
},

Now we can build the production version of the styles with

yarn run build;

As mentioned at the start if this article, the pre-built version of Tailwind is fairly large. In reality the one built by our build process is most likely even larger! To actually make our styles production ready we need to purge unnecessary styles, i.e. delete classes that are not used in our HTML files. This can be done with PurgeCSS (a dependency of Tailwind CSS).

To use PurgeCSS on our styles we need to configure Tailwind to look through our template files for the HTML classes we actually use.

Add the purge option to your tailwind.config.js and list you template and theme folders (if you are using a theme built with Tailwind).

module.exports = {
  purge: ["./templates/**/*.html", "./theme/**/*.html"],
  theme: {},
  variants: {},
  plugins: [],
};

You should also read the Tailwind docs on controlling file size to understand how the process works and what you need to take into account when writing your classes.

Another thing you need to consider for production builds is cache busting. It's best for performance to have the visitor browsers cache your stylesheets. This, however, necessitates some mechanism for bypassing the cache whenever the stylesheet actually changes.

Zola has a built-in method of bypassing caches by appending a hash calculated from the file contents at the end of a static asset url. This is enabled by using the cachebust=true parameter of the get_url method.

Unfortunately at the time of writing the get_url implementation has a bug when used with the cachebust parameter. See here for a workaround.

Cache busting combined with some cache control headers (e.g. on Netlify) means that the visitor browser only needs to fetch the stylesheet once every time it changes. And, since we are using Tailwind, that should not be often.

If you are using an external service to build and host your Zola site, remember to add the yarn run build before zola build in the build command. For example, if you deploy your site on Netlify, you can specify the build command in netlify.toml

[build]
publish = "public"
command = "yarn run build && zola build"

Finished, finally

We've now set up a system to compile and include styles build with Tailwind CSS to our site built with the Zola static site generator, and are ready get stylin'!

Got any questions, tips or special workflows of your own? Comment down below! (Github account required.)

Comments (6)

Add a Comment
  • Avatar of kartikynwa
    kartikynwa2020-11-03
    Thanks very much for this. Extremely helpful.
  • Avatar of mattijv
    mattijv2020-11-10
    @kartikynwa Happy you found the content useful. And thank you for validating that the comment system works!
  • Avatar of kartikynwa
    kartikynwa2020-11-10
    I had a question if you don't mind. Since Tailwind resets ALL styling (headings, lists, code blocks, etc.), I have been reconsidering it's aptness for a blog. Your blog looks very pretty so I was wondering how you dealt with that. Did you restyle all elements manually? Or did you seek help from a plugin? I ran into @tailwindcss/typography but I am still to look into its ease and degree of customisation.
  • Avatar of mattijv
    mattijv2020-11-11
    I actually quickly go over it in the post (see the Styling Markdown Content part), but yes I had to add styles for the elements generated from Markdown. That was not a huge hassle as I could use the Tailwind @apply directive to use the utility classes the same. All in all it's about 40 lines of additional styles (although I'm pretty sure I don't have all of markdown covered yet).

    The @tailwind/typography definitely looks like an attractive alternative to creating the styles yourself. I did consider it for the styling, but if I remember correctly it was not production ready at the time. Now it's probably a better choice.

  • Avatar of mckeever02
    mckeever022021-04-12
    Thanks for writing this! Really helpful.
  • Avatar of tchartron
    tchartron2021-12-24
    Hi ! Thanks for this article, I thought it is a good place to show the theme I made for Zola using tailwindcss https://github.com/tchartron/blow Hope you guys find this useful !