Writing A Static Site Generator

I wanted a clean, simple site. Blog-like. Not powered by some bloated overwrought monstrosity of a blogging platform with security vulnerabilities and updates to install and plugins to manage. Just a few web pages, not a Javascript web app. I wanted scripts, written from scratch by myself, to generate the static site pages. I didn't want to wrestle any sprawling web frameworks. I wanted to stay away from making things complicated. And you're reading the result.

To make this site I started with some goals:

And some more specific features I wanted to include:

For this first post, I'm going to walk through my thought process in building this site. The source code is available in my GitHub repo here.

Inspiration

The main inspiration for this site is zpjiang.me. I've been keeping my eye out for clean, minimalist sites for a while and this was my favorite I've seen.

Getting Started

I picked Bash scripts to generate the site. But... why? Because it's fast for me. Because I can move and copy and write a bunch of files without fancy syntax. And because I can call any number of scripts or programs easily. As an added bonus, I can use heredocs as a simple template system for the pages' headers and footers. Yes, I know heredocs aren't unique to Bash.

I grabbed normalize.css and the Markdown Perl script.

For a starting point for my site, I wrote a basic article page by hand, styles and all. And because the site is simple and static, no server is needed during development. I just opened the .html file in my browser to handcraft the page.

After I had the example page looking good, I created a directory structure for the project:

gen/

# static files will be copied as-is to the built site
gen/static/
gen/static/css/
gen/static/css/normalize-8.0.0.css
gen/static/css/style.css

# Markdown files that will become articles
gen/articles/

# each build will be written to a new directory here
out/

# Markdown
Markdown_1.0.1/

# script to run to build the site
build.sh

The initial build.sh script was basically a wrapper around a heredoc statement. It output the static files and the page content:

#!/bin/bash

THIS_SCRIPT="${0}"
THIS_DIR="$(dirname "${THIS_SCRIPT}")"
OUT_DIR="${THIS_DIR}/out/$(date +%Y-%m-%d-%H%M%S)"
GEN_DIR="${THIS_DIR}/gen"
STATIC_DIR="${THIS_DIR}/gen/static"

# make new build output dir
mkdir -p "${OUT_DIR}"

# put static files in place
cp -rp "${STATIC_DIR}"/* "${OUT_DIR}/"

cat > "${OUT_DIR}/index.html" << xxxxxEOFxxxxx
<!DOCTYPE html>
<html lang="en">
    <head>
    ...
    </head>
    <body>
    ...
    </body>
</html>
xxxxxEOFxxxxx

Then I moved the common header and footer HTML tags from build.sh into separate gen/header.sh and gen/footer.sh scripts. The body of the article could then be written using Markdown, between the header and footer:

#!/bin/bash

...

"${GEN_DIR}/header.sh" > "${OUT_DIR}/index.html"

perl "${MARKDOWN_PERL_SCRIPT}" --html4tags "${ARTICLE_MARKDOWN_FILE}" >> "${OUT_DIR}/index.html"

"${GEN_DIR}/footer.sh" >> "${OUT_DIR}/index.html"

...

Next I wanted to be able to process multiple Markdown files, one per article, and generate a separate page for each. To do this, I embedded meta data about the article at the top of each file using a Markdown comment syntax:

[//]: # (gen-title: Example Title)

[//]: # (gen-title-url: Example-Title)

[//]: # (gen-meta-end)

This is the start of an article written in Markdown.

...

The gen-title line can be used to the title to display on the page, and the gen-title-url can be used for the name of the HTML file itself. Is this ugly? Yes! But does it work? Yes!

I wanted to be able to sort articles by dates, so as a convention I just put each article's date in the filename in YYYY-MM-DD-N format, where N is the nth article written on that day. With this in place the build.sh script could figure out the date and title of each article. Then build.sh could loop over the article files and generate an .html file for each. The HTML <title> was set by adding one CLI argument to the header.sh script:

#!/bin/bash

...

find "${GEN_DIR}/articles" -type f | while read ARTICLE_MARKDOWN_FILE
do

    ARTICLE_METADATA="$(grep -B 99 '^\[//\]: # (gen-meta-end)' "${ARTICLE_MARKDOWN_FILE}")"

    ARTICLE_TITLE="$(echo "${ARTICLE_METADATA}" | grep -m 1 gen-title: | cut -d ' ' -f 4- | sed 's/)$//')"
    ARTICLE_TITLE_URL="$(echo "${ARTICLE_METADATA}" | grep -m 1 gen-title-url: | cut -d ' ' -f 4- | sed 's/)$//')"

    ARTICLE_DATE="$(basename "${ARTICLE_MARKDOWN_FILE}" | cut -d . -f 1)"

    "${GEN_DIR}/header.sh" "${ARTICLE_TITLE}" > "${OUT_DIR}/${ARTICLE_TITLE_URL}.html"

    perl "${MARKDOWN_PERL_SCRIPT}" --html4tags "${ARTICLE_MARKDOWN_FILE}" >> "${OUT_DIR}/${ARTICLE_TITLE_URL}.html"

    "${GEN_DIR}/footer.sh" >> "${OUT_DIR}/${ARTICLE_TITLE_URL}.html"

done

...

You get the idea. Nothing revolutionary. But I can wrap my head around it and make tiny tweaks wherever I want.

Sprucing it Up

I played around with color palettes on coolors.co until I found one I liked. I'm a terrible amateur in this department but that site worked really well.

In order to allow the color palette to be changed easily, I moved and renamed gen/static/css/style.css to gen/style.sh and converted it to a heredoc-based Bash script to output my custom style.css file. The palette colors can simply be inserted everywhere as variables in the heredoc syntax:

#!/bin/bash

CLR_PAGE_BG="#eff1f3"
CLR_TEXT_MAIN="#474e60"
...

cat << xxxxxEOFxxxxx
body {
    margin: 0;
    color: ${CLR_TEXT_MAIN};
    background-color: ${CLR_PAGE_BG};
    font-size: 1.0rem;
    line-height: 1.6;
    font-family: 'Helvetica Neue', Arial, sans-serif;
}

h1, h2, h3, .article-title {
    line-height: 1.3;
}

...
xxxxxEOFxxxxx

After moving adding one line to build.sh, I now have variables in the style sheet:

"${GEN_DIR}/style.sh" > "${OUT_DIR}/css/style.css"

Hosting

Edit: I removed an expired affiliate link here, but I'm still using and recommend Bluehost.

For web hosting, I'm using Bluehost. I've been a Bluehost customer for a couple months shy of 10 years now. I can't say it's the best hosting company because I honestly haven't had experience with that many. But 10 years later I can recommend them to anyone getting started with web hosting because they offer fixed-price hosting with unlimited bandwidth and they're easy to use.

GitHub

Because the static site generation scripts might be useful to others, the source is available on GitHub.