From Zero to Zola: Building My Personal Website from Scratch
After finishing a recent internship, I finally had the time to tackle a project I’d been looking forward to: building my personal website! This post is the story of my journey: choosing the right tools, battling CSS, and finding my way through the world of static site generators.
The Inspiration#
My first challenge wasn’t code, but a question: what should a personal website even look like? There are so many different kinds of personal sites out there, some with beautiful dynamic elements that do cool stuff when you scroll or move your mouse around (you know the ones I’m talking about)!
However, I’ve always felt like something simple and clean just looks better. One website that I’m a fan of is my friend William’s personal website - it’s clean with a nice personal touch! (Unfortunately, he has since changed his site, but you can still see the old version on the Wayback Machine 😂.)
Finding the Right Tools#
With zero frontend experience, my first hurdle was simply: how do you even create a website like that? The answer is simple - by using a static site generator (SSG)! An SSG allows you to create a website using content written in Markdown, and then it automatically generates the HTML and CSS for you. Although William has written his own SSG (check it out!) that he used to make his site, I decided to go with something more mainstream.
I started with Hugo, the most popular SSG (according to GitHub stars), but quickly realized pre-made themes were too restrictive. After spending much time trying to customize the Blowfish theme to my liking, I decided to build from scratch instead. I switched to Zola, which has a more intuitive templating engine (Tera) and surprisingly better documentation than Hugo.
Making the Website#
Although this was the first time I had used HTML and CSS, I decided to dive straight in. Fortunately, since static sites are, well, static, I could just inspect the source of other websites to see how different components were made. I wanted a reliable source that I could refer to for best practices and a great reference was the Serene Zola theme. Coming from a backend background, my secret weapon for bridging the frontend knowledge gap was Gemini Canvas. When starting out, I would be pretty clueless about how to implement a feature for the first time. However, I could easily ask Gemini Canvas to generate a small component (for example a cards section that popped out when you hovered over it), and then inspect and play around with the code in canvas mode to understand it. From there, I could modify the code to fit my needs, and gradually build up my website.
My first thought was “Wow, I wish I’d paid more attention in my HCI lectures!” Nothing that I envisioned looked as good in the browser as it did in my head. Surprisingly, the hardest part was choosing a colour scheme! I initially chose way too many colors, resulting in a site that looked like a child’s art project! I eventually settled on a simple but strict formula: an off-white background, dark text, and a single complementary color for highlights. This restriction gave me the clean, neutral look I was aiming for.
Initial Thoughts on HTML and CSS#
I’m a big believer of the line:
There should be one– and preferably only one –obvious way to do it.
from the Zen of Python.
However, CSS is very much the opposite of this philosophy. Having evolved very
much over the decades, CSS has accumulated a plethora of ways to achieve the
same result. Of course, one should only use modern CSS, but even then, there are
multiple ways to do the same thing. As a beginner, it was difficult to decide
— Should I use flexbox or grid for this layout? Should I use margin within
an element, or use gap in the parent element? However, as I became more
familiar, I started to embrace the flexibility of CSS.
Unlike backend logic, which can be difficult to isolate, I found that I could much more easily break down HTML and CSS into individual components. To debug an issue, I could always create a minimal working example and build up features until I found the root cause of the problem. Furthermore, the browser developer tools were a godsend! Being able to inspect elements, see the box model, and view applied styles made debugging so much easier.
The Problem with CSS Encapsulation#
Still though, I found it hard to maximise code reusability whilst maintaining readability. The partials and macros provided by Tera work great for being able to reuse HTML code. For example, I use the same ‘cards’ partial in both the homepage and posts listing section, and I use the same ‘post metadata’ partial to display the date, word count, and reading time for posts. This all works great, but the issue comes with integrating CSS. Zola supports SCSS, and I had the obvious idea of creating one SCSS file per partial to perfectly encapsulate the styles for that component. Of course, this fell apart when I actually tried to use the same component in different contexts!
The core issue is that the boundaries of encapsulation differ between the HTML and the CSS. While a component’s structure (HTML) is fixed, its style (CSS) often depends on where it is used. For example, I wanted my ‘cards’ partial to appear as a three-column grid on one page, but as a single column on another, with a different inner layout to match. This forced the parent page’s CSS to reach in and override the component’s defaults. The result was a leaky abstraction: the HTML component was perfectly reusable, but the CSS was tightly coupled to its context. While some advanced SCSS could bandage this issue, it highlights a fundamental flaw with this architecture. Zola’s template logic encourages componentization, but CSS’s cascading nature fights it. I learned that aiming for perfect encapsulation in a simple static site is a trap.
Setting up my Environment#
As a Neovim user, getting my editor configured for this new stack was its own
mini-challenge. Unfortunately, Neovim doesn’t have great support for Zola. My
HTML files kept being detected as htmldjango filetype, which resulted in
broken syntax highlighting until I added the following to my config to force
HTML filetype for .html files:
vim.filetype.add({
extension = {
html = "html",
},
})
Tera, being a general-purpose templating engine, doesn’t have a dedicated language server, but the syntax is so similar to Jinja2 that I was able to use the Jinja LSP. However, support was still poor, with no syntax highlighting for Tera tags, and no autocompletion. At least autoformatting worked fine! I tried out the Zola.nvim plugin but this had the opposite issue - great support for Tera syntax, but no support for HTML! Luckily, on the SCSS side (which was significantly more important for LSP support), the Some Sass LSP worked great. Therefore, the overall editing experience ended up being surprisingly alright and I fortunately did not have to make the dreaded switch to VSCode!
Buying a Domain and Hosting my Site#
Once my site was pretty much ready, it was time to actually host it outside of my local environment! With some quick Google searches, the general consensus seemed to be to avoid GoDaddy, and that Porkbun and Cloudflare are good, so I ended up choosing Porkbun since they had a discounted first-year price. Unfortunately, harrymin.com was already taken, so I had to settle for harrymin.dev. Oh well!
For hosting, I decided to go with Cloudflare Pages, which definitely had the
best free tier out of all the options I considered, with the only downside being
that you had to use Cloudflare’s DNS. Transferring the DNS over was pretty
straightforward though, and after setting up some analytics and a 301 redirect
for the www subdomain, my site was live! I had some slight issues with
building the site, but the Zola docs are great with individual guides and
troubleshooting for each major hosting provider, so fixing the build commands
was straightforward.
The only lingering issue is that I’m getting a net::ERR_CERT_AUTHORITY_INVALID
error when accessing the site on Eduroam. I think this is due to some security
feature as Cloudflare marks domains registered in the last 30 days as “New
Domains” and a
potential security risk
so fingers crossed this resolves itself in a few weeks!
Final Thoughts#
Building this site from scratch with Zola was a fantastic learning experience. While I started by looking at pre-made themes, the decision to build my own was far more rewarding. It forced me to actually learn HTML and CSS and gave me full control over the final product. Was it harder? Absolutely. But it was definitely worth it! I now have a site that is truly mine. Now that the plumbing is finally done, I’m excited to do the most important part: start writing!