This is the third generation of my website. It is currently built with Zola, an excellent static site generator written in Rust.
The previous generation of this website used Gatsby, a popular static site generator combining React with GraphQL. Using Gatsby was a poor decision. Gatsby has its place, but there’s no need to use it for a small personal blog unless you really want to learn how to use Gatsby for a larger project. I’m not a purist who would eschew something simply because it is complicated, because I understand that a complex problem will sometimes require a complex solution.1 But using Gatsby inappropriately for websites like this is a choice that novices are ill-prepared to build and more importantly, maintain.
Gatsby is fundamentally designed for big projects. The design patterns that it encourages are an over-engineered obstacle for the individual rather than a useful tool. The performance it touts also aren’t discernible unless you have some really beefy projects.
New developers who recently learn about the latest innovations in React or Node fall prey into becoming very impressionable. I can attest to this, because I was once this exact person, and this mindset drove me towards Gatsby.
I suppose it is tempting, if the only tool you have is a hammer, to treat everything as if it were a nail.
But there was a time before I knew what Gatsby, static site generators, or React was. My first websites in high school were very basic — I knew no way to handle automation or templating whatsoever, and thus much of my time was spent copying and pasting chunks of HTML and CSS boilerplate code. If I modified the navigation bar on my website, then I would have to repeat that edit on every file containing the navigation bar code.
Eventually, a friend of mine introduced me to Embedded Javascript, which was a game-changer because it enabled me to template large chunks of HTML. I now had my first taste of the Node ecosystem and could begin experimenting with packages and their functionality.
Generation 1.0 – EJS🔗
I bought the domain chengeric.com
during my senior year of high school. The first incarnation of this website lived on a Digital Ocean droplet running an Express server, which in turn hosted my little EJS website.
I didn’t know how to handle routing in a generic way, so every time I added a new page to the website I would have to manually add the path to a steadily growing index.js
.
'use strict';
module.exports = function (app) {
...
app.get('/newpage', function (req, res) {
res.render('newpage');
});
...
My index.js
file was just several dozen lines of manually declared routes. I could have implemented some kind of router, but this was beyond my knowledge and curiosity at the time.
This strategy had a number of big disadvantages. Every time I updated my website, I would have to complete a very manual routine:
- push my changes into GitHub
- SSH into the server
git pull
the latest changes- restart the Node.js daemon
I was also paying Digital Ocean $5/month for this droplet, which was completely unnecessary when GitHub Pages and similar services will host your quiet static blog on a wonderful CDN for free.
This was a very manual process — I was using Node to the full extent of my unsophistication. But I knew this website could be built much better, and what more noble purpose can a side project fulfill than by teaching you something meaningful?
Generation 2.0 – Gatsby🔗
The first ‘generation’ of my website was focused on projects I worked on during high school. Now I was more keen about writing, and by 2019, I had enough of my tedious workflow that was wholly inadequate for blogging. I learned about static site generators and how Gatsby was super powerful and extensible.
I found a well-received Gatsby starter for the new incarnation of my website, and got to work adjusting the theme to my taste.
Let’s see what this theme consists of, without any modification yet.
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Dockerfile.development
├── Dockerfile.production
├── LICENSE
├── README.md
├── config.js
├── content
│ ├── pages
│ │ ├── about.md
│ │ └── contacts.md
│ └── posts
│ ├── 2016-01-09---Perfecting-the-Art-of-Perfection.md
│ ├── 2016-01-12---The-Origins-of-Social-Stationery-Lettering.md
│ ├── 2016-02-02---A-Brief-History-of-Typography.md
│ ├── 2017-18-08---The-Birth-of-Movable-Type.md
│ └── 2017-19-08---Humane-Typography-in-the-Digital-Age.md
├── docker-compose.development.yml
├── docker-compose.production.yml
├── flow
│ └── css-module-stub.js
├── flow-typed
│ └── npm
│ ├── classnames_v2.x.x.js
│ ├── gatsby_vx.x.x.js
│ ├── jest_v24.x.x.js
│ ├── lodash_v4.x.x.js
│ ├── netlify-cms-app_vx.x.x.js
│ ├── react-disqus-comments_vx.x.x.js
│ ├── react-helmet_vx.x.x.js
│ └── react-test-renderer_v16.x.x.js
├── gatsby
│ ├── create-pages.js
│ ├── on-create-node.js
│ ├── on-render-body.js
│ └── pagination
│ ├── create-categories-pages.js
│ ├── create-posts-pages.js
│ └── create-tags-pages.js
├── gatsby-browser.js
├── gatsby-config.js
├── gatsby-node.js
├── gatsby-ssr.js
├── jest
│ ├── __fixtures__
│ │ ├── all-markdown-remark.js
│ │ ├── markdown-remark.js
│ │ ├── page-context.js
│ │ └── site-metadata.js
│ ├── __mocks__
│ │ ├── file-mock.js
│ │ └── gatsby.js
│ ├── jest-config.js
│ ├── jest-preprocess.js
│ └── loadershim.js
├── netlify.toml
├── package.json
├── postcss-config.js
├── renovate.json
├── scripts
│ ├── update_changelog.sh
│ └── update_release_notes.sh
├── src
│ ├── assets
│ │ └── scss
│ │ ├── _base.scss
│ │ ├── _mixins.scss
│ │ ├── _variables.scss
│ │ ├── base
│ │ ├── init.scss
│ │ └── mixins
│ ├── cms
│ │ ├── index.js
│ │ └── preview-templates
│ │ ├── page-preview.js
│ │ └── post-preview.js
│ ├── components
│ │ ├── Feed
│ │ │ ├── Feed.js
│ │ │ ├── Feed.module.scss
│ │ │ ├── Feed.test.js
│ │ │ ├── __snapshots__
│ │ │ └── index.js
│ │ ├── Icon
│ │ │ ├── Icon.js
│ │ │ ├── Icon.module.scss
│ │ │ ├── Icon.test.js
│ │ │ ├── __snapshots__
│ │ │ └── index.js
│ │ ├── Layout
│ │ │ ├── Layout.js
│ │ │ ├── Layout.module.scss
│ │ │ ├── Layout.test.js
│ │ │ ├── __snapshots__
│ │ │ └── index.js
│ │ ├── Page
│ │ │ ├── Page.js
│ │ │ ├── Page.module.scss
│ │ │ ├── Page.test.js
│ │ │ ├── __snapshots__
│ │ │ └── index.js
│ │ ├── Pagination
│ │ │ ├── Pagination.js
│ │ │ ├── Pagination.module.scss
│ │ │ ├── Pagination.test.js
│ │ │ ├── __snapshots__
│ │ │ └── index.js
│ │ ├── Post
│ │ │ ├── Author
│ │ │ ├── Comments
│ │ │ ├── Content
│ │ │ ├── Meta
│ │ │ ├── Post.js
│ │ │ ├── Post.module.scss
│ │ │ ├── Post.test.js
│ │ │ ├── Tags
│ │ │ ├── __snapshots__
│ │ │ └── index.js
│ │ └── Sidebar
│ │ ├── Author
│ │ ├── Contacts
│ │ ├── Copyright
│ │ ├── Menu
│ │ ├── Sidebar.js
│ │ ├── Sidebar.module.scss
│ │ ├── Sidebar.test.js
│ │ ├── __snapshots__
│ │ └── index.js
│ ├── constants
│ │ ├── icons.js
│ │ ├── index.js
│ │ └── pagination.js
│ ├── hooks
│ │ ├── index.js
│ │ ├── use-categories-list.js
│ │ ├── use-site-metadata.js
│ │ └── use-tags-list.js
│ ├── templates
│ │ ├── __snapshots__
│ │ │ ├── categories-list-template.test.js.snap
│ │ │ ├── category-template.test.js.snap
│ │ │ ├── index-template.test.js.snap
│ │ │ ├── not-found-template.test.js.snap
│ │ │ ├── page-template.test.js.snap
│ │ │ ├── post-template.test.js.snap
│ │ │ ├── tag-template.test.js.snap
│ │ │ └── tags-list-template.test.js.snap
│ │ ├── categories-list-template.js
│ │ ├── categories-list-template.test.js
│ │ ├── category-template.js
│ │ ├── category-template.test.js
│ │ ├── index-template.js
│ │ ├── index-template.test.js
│ │ ├── not-found-template.js
│ │ ├── not-found-template.test.js
│ │ ├── page-template.js
│ │ ├── page-template.test.js
│ │ ├── post-template.js
│ │ ├── post-template.test.js
│ │ ├── tag-template.js
│ │ ├── tag-template.test.js
│ │ ├── tags-list-template.js
│ │ └── tags-list-template.test.js
│ ├── types
│ │ └── index.js
│ └── utils
│ ├── get-contact-href.js
│ ├── get-contact-href.test.js
│ ├── get-icon.js
│ ├── get-icon.test.js
│ └── index.js
├── static
│ ├── admin
│ │ └── config.yml
│ ├── css
│ │ ├── katex
│ │ │ ├── fonts
│ │ │ └── katex.min.css
│ │ └── prismjs
│ │ └── theme.min.css
│ ├── media
│ │ ├── 42-line-bible.jpg
│ │ ├── cpu.svg
│ │ ├── gutenberg.jpg
│ │ ├── image-0.jpg
│ │ ├── image-1.jpg
│ │ ├── image-2.jpg
│ │ ├── image-3.jpg
│ │ ├── image-4.jpg
│ │ ├── movable-type.jpg
│ │ ├── printing-press.jpg
│ │ └── type-through-time.jpg
│ └── photo.jpg
└── yarn.lock
Wow 😅! What a fun time trying to initially grok the structure of this project. One of my first concerns was why I had so many seemingly vestigial configuration options. What do each of the gatsby-*.js
files mean? Does a static blog designed to be served with GitHub Pages really need server-side rendering? Managing the complexity of this setup felt like operating a 747.
The modularity of this codebase ended up becoming very unwieldly; I would really have to go fishing to find the component that I wanted to edit the appearance of. This significantly hindered my ability to form a mental map of how/where/why things worked.
One day, I wanted to add a “X min reading time” label to my articles. After installing gatsby-remark-reading-time
, I had to edit my gatsby-config.js
and then create an appropriate GraphQL query to generate the reading time itself for each post. This ended up requiring a lot of time fighting GraphQL to do what I needed. GraphQL seems like a promising tool, but it only ended up adding additional moving parts (and points of failure) to an already unsteady foundation.
This next gripe is more of a npm woe. I thought that using dependabot to automatically update my dependencies would alleviate some of my maintenance workload, but this disfigured my package.json
so badly that it was completely impossible to restore a working environment.
At this point, I had deviated so far from the original Gatsby starter that simply trying to mimic the original dependency set wouldn’t work, because the original repo is also broken and unmaintained now. A few months passed between inadvertently scuttling this website and finally rebuilding it in Zola. During that time, I couldn’t edit this website at all, which was deeply frustrating whenever I had the inspiration to write a post or add something interesting to my reading list.
Even if I had a much stronger and intricate understanding of how my dependency graph worked, it would have still probably taken me hours to unblock myself. Hours that I could have spent being productive, or better enjoying my leisure time.
Generation 3.0 – Zola🔗
I now use Zola, which packages all the functionality you would need (and like) for a static site — Sass compilation, syntax highlighting for code blocks, image optimization, markdown support, table of contents, and more — into a simple binary. These features would have each demanded a battery of npm packages to achieve the same functionality, and risks shattering whenever some maintainer gets bored and stops updating their npm package.
The “X min reading time” that I spent an hour trying to implement in Gatsby? With Zola, it’s automatically provided in post metadata, so all I got to do is simply reference reading_time
in my HTML template.
If I wanted to edit this website from a fresh setup, I need to run the following commands:
$ git clone git@github.com:eh8/MY_WEBSITE
$ cd MY_WEBSITE
$ zola serve
Building site...
Checking all internal links with anchors.
> Successfully checked 1 internal link(s) with anchors.
-> Creating 8 pages (0 orphan) and 1 sections
Done in 21ms.
Web server is available at http://127.0.0.1:1111
I now have a browser tab open with live-reload enabled, ready to go. From start to finish, this whole process took about 15 seconds. Herein lies the two greatest benefits of Zola over Gatsby for small blogs: it’s super fast and super simple. Contrast this workflow to running npm install
, waiting a few minutes for the command to churn, and then hoping npm start
works on the first shot.
Granted, I didn’t need to run npm install
every time I edited my site. But even the time spent waiting for npm start
is non negligible.
Zola is considerably faster than Jekyll and indiscernibly slower than Hugo for a basic blog like this website. It doesn’t need much tweaking because its default “batteries included” feature set is so strong. I also considered Eleventy, but found it too complicated and yet somewhat lacking in features that Zola provides natively.2
I would rather spend my time building a non-Node website that requires a higher fixed learning cost, than having to spend my time afterwards regularly fighting dependency errors and deprecated settings on a Node website that was initially easy to spin up. But as it turns out, Zola can be learned in an afternoon or two, so I didn’t have to pay much of a fixed learning cost after all.
With Zola, I don’t need to worry about deleting node_modules/
3 as a last-ditch way of getting my site to work, and then waiting for npm install
to rebuild my dependencies. I can confidently write from macOS and Arch Linux interchangeably with the exact same procedures and tooling. I know that I can neglect this website for months on end and return to a productive workflow immediately.
Every push to this website’s repository triggers this GitHub action, which rebuilds the site in about 35 seconds from the moment I push.
on: push
name: Build and deploy on push
jobs:
build:
name: shalzz/zola-deploy-action
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: shalzz/zola-deploy-action
uses: shalzz/zola-deploy-action@master
env:
PAGES_BRANCH: gh-pages
BUILD_DIR: .
BUILD_THEMES: false
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
A comparable Node workflow running npm install
and gatsby build
would probably last 10x longer. And with this saved time, I get to think more about writing stuff.
For instance, you can toggle HTML and CSS minification in Zola by enabling minify_html = true
in config.toml
. Achieving the same effect in Eleventy requires installing some npm plugins and configuring them with some code you’re inevitably going to copy and paste from Google.
Which would often exceed 1 GB