Deploying Eleventy and Tailwind CSS to Netlify - Part 1

A quick start guide for your 2021 blog

As part of my slew of goals for 2021, I spent Slack's (incredible) winter break finally focusing on rebuilding my presence on the web. In the past, I've used either Wordpress, Jekyll, Django for most of my web development needs. In the past few years, however, Jamstack has really taken off, so I decided to try my hand at something new - deploying a modern statically generated site using Eleventy and Tailwind hosted for free by Netlify.

I found that there were a slew of starter projects out there for inspiration, but I really wanted to learn the stack from top to bottom myself, so I decided to start from scratch. Additionally, many of the starter projects are fairly opinionated (and heavy). While this can be useful, my site is pretty basic so I decided to keep things simple.

I used the following projects for inspiration:

Getting started was pretty easy - ensure you have node.js installed and create a new directory on your machine to store your site in (I called mine simple-eleventy). Then, you can get things started:

$ mkdir simple-eleventy
$ cd simple-eleventy
$ npm init

NPM will walk you through some prompts, you can accept the defaults (or providey our own preferences) for everything. Once that's done, install your dependencies:

$ npm install @11ty/eleventy @fullhuman/postcss-purgecss \
 autoprefixer luxon npm-run-all postcss-cli tailwindcss

Here's what we just installed:

  • @11ty/eleventy - The eleventy static site generator.
  • @fullhuman/postcss-purgecss - The PostCSS plugin for PurgeCSS. This allows us to compile our Tailwind CSS during the build, and only generate the CSS that is actually used in our HTML templates (to minimize the size of the CSS file)
  • autoprefixer - A PostCSS plugin to provide vendor prefixes to our CSS rules for the best browser compatibility.
  • luxon - A better way for us to work with dates and times in Javascript. This is important as eleventy is a bit messy when it comes to content dates.
  • npm-run-all - This is an easy way for us to run multiple build commands in parallel (for instance, watching + regenerating our CSS via PostCSS while also watching + regenerating our HTML with eleventy).
  • postcss-cli - Lets us run PostCSS via the command line (for generating our CSS during the build process)
  • tailwindcss - A great CSS framework for rapidly building custom interfaces (this site is built with Tailwind)

At this point, all we have is our node files in the directory. Before we go further, we should create our git repo so that we can push this to GitHub. We'll start with creating a .gitignore file at the top-level directory, which will list the files that we should not store in version control:

.gitignore

# .gitignore
# node.js artifacts
node_modules
# Build artifacts
dist
.tmp
.env
# Random files your computer may generate
.vscode
.DS_Store
.AppleDouble
.LSOverride

We can now create our git repo:

$ git init
$ git add .
$ git commit -m "Initial Commit"
$ git branch -M main

With that out of the way, we'll start with creating our site. The source files for our site will be located in src/site. Both PostCSS and Eleventy will watch this folder. When CSS files change, PostCSS will compile them and store the generated CSS file in .tmp/css/style.css. Eleventy will watch the generated .tmp/css/style.css file as well as the src/site folder, and when anything in either changes it will generate the entire static site in to a folder called dist.

We'll start with creating a super basic folder layout:

$ mkdir -p src/site
$ cd src/site
$ mkdir -p _includes/layouts
$ mkdir -p _includes/css
$ mkdir blog

In our src/site folder, we now have a few new folders:

  • _includes/layouts, which will contain our HTML layouts, written in the Nunjucks template language.
  • _includes/css, which will contain our source CSS files
  • blog, which will contain the content for blog posts on our site.

We'll start with creating a simple HTML layout. In Eleventy, we can create multiple template layouts that can inherit other layouts. So, for instance, a base layout that contains the basic HTML for our site, a post layout for specific HTML we want on blog posts, and a list layout that contains a chronological listing of all of our posts.

We'll start with creating our base layout:

src/site/_includes/layouts/base.njk

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,
    initial-scale=1.0">
    <link rel="stylesheet" href="/static/css/style.css">
    <title>{{ title }} | My Site</title>
  </head>
  <body>
  <header>
      <a class="/">My Site!</a>
  </header>

{{ content | safe }}

</body> </html>

We'll also go ahead and create a basic home page:

src/site/index.md

---
title: Home
layout: base
---
# Hi there!

Welcome to my site!

Finally, we'll prepare some basic CSS. In this file, we're just going to import tailwind and turn our H1 headers blue (to show that it's working!):

src/site/_includes/css/style.css

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base { h1 { @apply text-blue-500; } }

With that out of the way, we can go back and configure tailwind, postcss, and eleventy and generate our first page. This all happens in our main folder (not in src/site).

We'll start with our tailwind config. All we're going to do here is define which files in our src/site folder contain HTML, so that PurgeCSS can make sure to check them and include the right classes in our generated HTML:

tailwind.config.js

// tailwind.config.js
module.exports = {
  purge: {
    content: [
      "src/**/*.html",
      "src/**/*.njk",
      "src/**/*.md"
    ],
    options: {
      whitelist: [],
    },
  },
  theme: {},
  variants: {},
  plugins: [],
};

We'll also create our PostCSS config. All this does is tell PostCSS to use Tailwind and autoprefixer:

postcss.config.js

module.exports = {
  plugins: [
    require(`tailwindcss`)(`./tailwind.config.js`),
    require('autoprefixer')
  ],
};

Then, we'll create our eleventy config. Don't forget to put the period at the start of the filename:

.eleventy.js

module.exports = function(eleventyConfig) {

// Create an alias for the base layer so we can reference it as base rather than the full paht. eleventyConfig.addLayoutAlias('base', 'layouts/base.njk');

// As our CSS is generated in to a folder that git ignores, we want to make sure eleventy still reads it eleventyConfig.setUseGitIgnore(false);

// Watch our generated CSS file for changes eleventyConfig.addWatchTarget("./.tmp/css/style.css"); // If it changes, write it to our generated full site eleventyConfig.addPassthroughCopy({ "./.tmp/css/style.css": "static/css/style.css" });

return { dir: { // Where to look for our site input: "src/site", includes: "_includes", // Where to place our generated site output: "dist" }, passthroughFileCopy: true, templateFormats : ["njk", "md"], htmlTemplateEngine : "njk", markdownTemplateEngine : "njk", }; };

Almost there! We just need to configure the commands used to build our site. We do this in our node.js package.json file. This should have been generated by npm init. The only thing you need to edit is the scripts section:

package.json

{
  "name": "simple-eleventy",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
      "build": "npm-run-all build:css build:html",
      "build:css": "postcss src/site/_includes/css/style.css -o ./.tmp/css/style.css",
      "build:html": "eleventy",
      "watch": "npm-run-all --parallel watch:css watch:html",
      "watch:css": "postcss src/site/_includes/css/style.css -o ./.tmp/css/style.css --watch",
      "watch:html": "eleventy --serve --port=8182"
    },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@11ty/eleventy": "^0.11.1",
    "@fullhuman/postcss-purgecss": "^3.1.3",
    "autoprefixer": "^10.1.0",
    "luxon": "^1.25.0",
    "npm-run-all": "^4.1.5",
    "postcss-cli": "^8.3.1",
    "tailwindcss": "^2.0.2"
  }
}

We're ready! You can see what your site looks like by running npm run watch. This creates a local server and hosts your site. Any changes you make are reflected in real time:

$ npm run watch

If successful, you should see a message like this:

[Browsersync] Access URLs:
 --------------------------------------
       Local: http://localhost:8182
    External: http://192.168.1.1:8182
 --------------------------------------
          UI: http://localhost:3003
 UI External: http://localhost:3003
 --------------------------------------
[Browsersync] Serving files from: dist

Open your browser and browse to http://localhost:8182 and you should see our (simple) site:

Your site rendered on localhost! You can see our title is blue as requested!

Posting to Netlify

Finally, we'll post our simple site to Netlify for free. We'll create one final file, netlify.toml, to contain our Netlify configuration:

netlify.toml

[build]
  command = "npm run build"
  publish = "dist"

[context.production] environment = { NODE_VERSION = "15.2.0", NODE_ENV = "production" }

[dev] command = "npm run watch" framework = "#custom" publish = "dist" port = 3000 targetPort = 8182 NODE_ENV = "development"

If you haven't already, create a repository on GitHub (or any other Git hosting site), and push up your website. Then, log in (or create an account) on Netlify and click the "New site from Git" button (or go here: https://app.netlify.com/start).

Creating a new site on Netlify Click the Git provider of your choice

Once you've connected to your git provider, you'll be able to select your repository:

Netlify Repo selection If you have a lot of repos like me, use the search bar

Netlify Repo settings Should be pre-populated from our netlify.toml file

Click Deploy site and we're off to the races! You'll be taken to your site settings page and will see your process under Production deployes. In my case, I went to Site settings > General > Site Details > Change site name to give my site a slightly friendlier name. After a few second, you should see your site is Published, and you can go to the URL at the top of the page to see it live!

Netlify Successful Deploy Our site is live!

One of the cool things you can do is branch deploys. In short, for every branch in your repo you create, Netlify will deploy a new version of your site. In my case, I'm continuing to work on simple-eleventy but wanted to provide you a snapshot of what the site looks like at the time of this writing. I simply created a new branch and pushed it to GitHub:

$ git checkout -b initial_deploy
$ git push origin initial_deploy

Netlify automatically picked up on this and started a new deploy of this branch (you can see it under the "Deploys" section of the site settings.)

Netlify Branch Deploy A branch deploy is a snapshot of your site at a certain time. It doesn't replace your main site

By clicking "Preview deploy", you can see what this site looked like at a specific time. For instance, here's my (live) URL for the initial_deploy branch: https://5feb7ac56514db0007617c58--simple-eleventy.netlify.app.

Even as I continue working on simple-eleventy in future blog posts, this URL will always be available.

I've published the work I've done so far on simple-eleventy to https://github.com/seanherron/simple-eleventy. Over the next few weeks, I'll follow up with additional blog posts on more styling, creating posts inside your repo, and more.