For the last 4 years, my personal blog was built using Hugo, one of the most popular static site generators. Back then I wanted to just start writing content about R and publish it quickly and easily into my own blog. It worked out well, since Hugo is an excellent tool for quickly spinning up a static site. However, over time, I began to feel restricted by its framework and frustrated by the bugs, and decided to take on the challenge of building my blog from scratch. The journey has not only given me more control but has also helped me sharpen my skills in web development, specifically in Clojure.
Here I want to share and register part of the experience to look back in a few years time and to inspire others to take the same challenge. I really get a gratifying feeling each time I build some service on my own.
You can have a look at the source code on my github under the repo blog.teoten.com.
Why Move Away from Hugo?
Hugo offers a fantastic set of features out of the box: it's fast, flexible, and supports a variety of formats like Markdown and Org-mode, making it an ideal tool for many developers. However, as my blog evolved, I started to encounter some limitations that motivated me to look for a more personalized solution.
It happened a few times that, without updating anything in particular, I was getting bugs about some specific line of code in a template, or even weirder bugs that I did not understand. I managed to solved them by rolling back or opening issues in the GitHub repositories and receiving support.
Nevertheless, a couple of weeks ago, after a long break from publishing due to family duties, I came back full of inspiration, wishing to share my new learned skills in Clojure, just to encounter one more bug related to the templates. Only that this time I told myself that it was enough, and that it shouldn't be so difficult to build my own site with such a great tool as Clojure. So, I decided to say goodbye to Hugo for good.
Disadvantages of Using a Framework Like Hugo
I am left very thankful to Hugo and all the team behind it, as well as the big bunch of people creating themes and sharing them for free. It is a very nice community that hold my project for 4 full years with minimal frustrations. I would totally recommend Hugo to any person wishing to start a blog quickly and easily without knowledge of the stack, or even with very little knowledge of programming in general.
However, as any framework, it also has its own disadvantages. In my experience, these were some of them:
-
Lack of Control: Hugo abstracts away a lot of complexity, which is great when you're just starting out. But this abstraction comes at a cost. When things break, whether it's related to a theme, the build process, or some obscure plugin, you're often left in the dark. Debugging can become frustrating without full knowledge of what's happening under the hood.
-
Theme Dependency: The reliance on external themes was another drawback. While themes provide flexibility, they often contain extra features that I didn't need, and customizing them wasn't always straightforward. I found myself constantly searching for fixes to theme-related issues, which added unnecessary complexity.
-
Stack Ignorance: Hugo is written in Go, a language I am not familiar with. While I could use it without knowing the language, I felt disconnected from my own website's build process. This lack of deeper understanding led me to feel like I didn't truly own my site's stack.
-
Feature Overload: Hugo provides an overwhelming number of features, many of which I never used. I began to crave a more lightweight, minimalist solution where I had full control over every aspect of the site.
What I Wanted Instead:
-
More Control: By building my own static site generator, I could understand every line of code, making it easier to troubleshoot and customize exactly the way I wanted.
-
Improved Skills: I wanted to challenge myself and improve my coding skills, particularly with clojure, frontend development, as well as learn more about search engine optimization (SEO).
-
Ownership of the Stack: Rather than relying on a pre-built framework, I wanted to fully understand and control the process of converting content into a static website.
Therefore, I embarked myself in a pretty fun journey of building my own site.
Using clojure to build a static site
I started learning Clojure at the beginning of 2024, and it's been fascinating. It has many of the qualities of the Lisp languages, which taught me functional programming and give me my everyday coding tools (Emacs), but it comes with some advantages by being host in the JVM and thanks to its native design to overcome some of the downsides of Common Lisp. Therefore, it was a natural choice for building my blog.
My first search for resources to get started showed me that there are a few good frameworks to build static websites and blogs with Clojure as well. But since frameworks was one of the things I wanted to avoid, I went on looking. I found this excellent tutorial by Christian Johansen about building with stasis and followed it to have my first site generator up and running after one evening of coding. However, his tutorial does not give me half of the tools I needed and wanted to use. It is based on markdown files, which I use, but I use org too. It is built using leiningen and I wanted to use the built in CLI and deps.edn. I was also wishing to generate an RSS feed in xml, to have a config.edn
file and set up environments, among others.
After initializing a git repo with the guide, I moved forward on my own and started crafting pieces here and there. This approach gave me the flexibility I was craving and the chance to dive deeper into programming concepts I hadn't previously explored.
My personal Stack
{:paths ["src" "resources"]
:deps {org.clojure/clojure {:mvn/version "1.11.1"}
stasis/stasis {:mvn/version "2023.11.21"}
hiccup/hiccup {:mvn/version "2.0.0-RC3"}
ring/ring-core {:mvn/version "1.12.2"}
ring/ring-jetty-adapter {:mvn/version "1.12.2"}
markdown-clj/markdown-clj {:mvn/version "1.12.1"}
optimus/optimus {:mvn/version "2023.11.21"}
enlive/enlive {:mvn/version "1.1.6"}
clygments/clygments {:mvn/version "2.0.2"}
org.jsoup/jsoup {:mvn/version "1.18.1"}
cprop/cprop {:mvn/version "0.1.20"}
selmer/selmer {:mvn/version "1.12.61"}
clj-time/clj-time {:mvn/version "0.15.2"}
org.clojure/data.xml {:mvn/version "0.0.8"}
hickory/hickory {:mvn/version "0.7.1"}
clj-org/clj-org {:mvn/version "0.0.3"}}
:aliases
{:dev {:extra-paths ["dev"]}
;; Call with `clj -X:build-site`
:build-site {:exec-fn teoten.ttblog.core/-main
:exec-args {:env :builder}}
;; clj -X:test
:test {:extra-paths ["test"]
:extra-deps {lambdaisland/kaocha {:mvn/version "1.91.1392"}
;; midje/midje {:mvn/version "1.10.10"}
}
:exec-fn kaocha.runner/exec-fn
:exec-args {:skip-meta :slow}}
}}
Here's a quick breakdown of my stack:
-
Content Parsing: I'm using Markdown, Org-mode, and raw HTML files generated by the native org-mode on Emacs. These three formats are then parsed into HTML using tools like markdown-clj and clj-org, which handle the different content types effortlessly. Furthermore, I can choose between using a plain org file, or its resulted parsed file in html format. This helps me to overcome some of the limitations of the clojure org parser (which are many) and allows me to use the power of Emacs and org-mode themselves.
-
Static Site Generation: The core of my site generation relies on stasis, a minimalistic Clojure library for creating static websites. It handles routing, file generation, and serves as the glue that ties the content to the final HTML pages.
-
HTML Templating: I'm using Selmer for HTML templating and Hiccup/Hickory for DOM manipulation. These tools give me fine-grained control over how my pages are structured and rendered.
-
RSS Generation: I generate the blog's RSS feed with Clojure's data.xml library, ensuring that my content can be syndicated properly.
-
Functional programming: My builder holds very little state (clojure's
atom
) and relies on functions, which allows me to do error handling, unit testing and refactoring like a breeze. Additionally, I can use support of generative AI by simply asking for "a function that takes x arguments to achieve z results", which keeps my mind free of worries about side effects or inheritance, and instead can focus only on the potential bugs and error handling, easily managed by the unit test.
-
Server for development: I am using ring adapter jetty from the ring library to have a live view of my blog during development and drafting. This means that I can be typing a new post and see my changes directly on the browser by just refreshing the page. Or refactoring some old functions and after evaluating them, refresh the browser and see the results.
-
CSS Styling: One of the most satisfying parts has been designing the CSS for the site from scratch. I have used Bootstrap and Tailwind in the past, but again, frameworks. Using my own custom CSS not only makes the site unique but has also taught me a lot about styling and responsive design.
Challenges
The first challenge I encountered was the use of Enlive by the tutorial. I followed along, and used some pieces of code from it, which just "worked", but I couldn't wrap my head around it. Since I wanted to have more control over my stack, I went ahead and try to learned it by creating a function to do something specific (at this point I have forgotten what exactly) but I couldn't. I looked into the repository and documentation just to find out that there was an open issue with a similar problem, and it was no solved. Then I realized that the last commit was 5 years ago, and there are 21 open issues at the moment of writing this.
I kept the working code and moved away from Enlive for further DOM manipulation, and found some help for using Jsoup, a Java library that I can call from Clojure (advantages of JVM hosted). It gave me the tools I needed but I didn't enjoy it as much as writing pure Clojure. Plus, I could barely have the work done, without fully understanding the code I was writing. Luckily, I came across hickory when I needed to do more manipulation, and it is what I am using now together with hiccup, which I'm more used to from working with Clojurescript. Hickory is more intuitive, documented and clojure-like, providing flexibility in the data structures to work with Hickory or Hiccup-style, both of which are very "clojurist" and are easy to use.
Another big challenge for me was the creation of a custom CSS, which I really wanted to do to improve my skills. So far I had worked with CSS by copying snippets of code, or using Bootstrap or Tailwind and then having a minimal CSS file for granular details. But building the CSS from scratch has been quite a challenge, which I decided to face with the help of generative AI. I been using ChatGPT and Codeium to ask for advice, get code snippets and solve problems with my styles. Although challenging, I have learned a great deal about styling with CSS, using variables, responsive design and the importance of the div
tag.
The Satisfaction of Building from Scratch
Building my blog from the ground up has been both fun and educational. I've learned a ton about Clojure, from how to manipulate data structures to leveraging libraries that handle everything from routing to RSS generation. I now understand almost every step of my site generation process (I still need to change a few pieces of Enlive and Jsoup code) and can troubleshoot issues quickly without having to rely on external frameworks.
That said, there's still plenty of work ahead. My frontend design is far from polished, and my SEO is still a work in progress. I also need to implement sections for tags and categories to improve content navigation. And the generator itself needs more work in the unit test and error handling. However, despite these shortcomings, I feel incredibly satisfied and fulfilled knowing that I built this system by myself.
After following the tutorial from Johansen, I thought that I could have my blog up and running, with minimal functionality, in a week. It anyway took me 2 weeks to reach a version I was satisfied with, which is not long considering that I had my expectations as high as my demands.
For anyone considering a similar path, I highly recommend taking the plunge. It's an amazing opportunity to grow as a developer, and the control and customization options you gain are worth the effort. I will share some of my experiences with pieces of the builder as the time goes, but feel free to reach out and ask for directions, support or particular topics you could be interested in.