Tejas Jadhav


Moving over to Hugo

I’ve been writing here since 2016. Back then, this blog was made using Ghost which was hosted on a cheap private VPS (LunaNode) that I found on LowEndBox.com. Though the VPS costed me $10/month, it was not a waste of money since I was mostly using that VPS for other hobby projects and it also offered a free email server for my custom domain.

As the months passed, my hobby projects died out. Additionally, the email server provided by LunaNode felt a bit rudimentary in comparison to full fledged solutions like Zoho which offered a free tier. Maintaining a VPS just for the blog felt a bit wasteful. But what was the alternative?

Issues with content management systems

Concept of having a dedicated server just for a blog felt a bit odd to me. The older content hardly changes, I don’t write much and neither do I have a team of writers who write for me, blog content is almost always static and does not have dynamic components (like XHR calls, interactive elements), my drafts are usually in some notepad or Google Docs that I write whenever I have something on my mind. I have no use of most of the functionalities that a full-fledged CMS like Ghost offers like user management, draft management, subscription management, plugins, analytics.

On the other hand, I wanted more control over how my content is presented, especially components like code blocks, which is not that straightforward with CMSes. Additional bloat that CMSes add impact page loading times and make the website appear a bit sluggish.

There was also the additional maintenance of running the database and the web server. They had their own set of hardware requirements. In case of Node.js based web servers, you need to run them behind a process management tool like Systemd or pm2 and also need reverse proxies like Nginx for hosting the static content. And finally, there is the hassle of setting up SSL and managing certificate rotation.

I almost went back to writing HTML files for each article by hand in an attempt to keep it extremely simple, but halfway through, I realised I’m spending more time in the formatting concerns rather than the writing itself.

Static site generators

If I was comfortable using VSCode and Markdown for writing articles, all I had to figure out was a way to automatically convert my Markdown files into HTML along with all the theming and URL management. That’s when I stumbled upon static site generators like Gatsby.

I was blown away initially with how easy it was to get what I wanted. Be it Google Analytics, syntax highlighting, Disqus comments, full Github Flavored Markdown support. There was a plugin for each use case. In fact, with MDX, I could create my custom own React components and embed them inside my articles if needed.

After investing a considerable amount of time, I managed to setup the whole blog and migrated all my Ghost articles. It went much better than I expected. Also, since the overall setup was so minimal, I wrote my own CSS theme for the blog in less than 100 lines!

However, there were minor hiccups at time of setting up, specifically related to Node.js dependencies. The dependencies clashed and after pulling my hair out while resolving those, I got it working. There were another set of nightmarish cases, especially while dealing with vulnerability warnings that NPM gave, which NPM screwed up even more after running npm audit fix.

But overall, I had a working blog which did not need a dedicated web server. My blog was hosted on a private Github repository and I used Netlify to host the website.

Issues with the Node.js based static site generators

While I initially thought that the issues I encountered while setting up where one-off cases, it soon became a recurring problem. For quite some time, I did not write much on this blog as I got occupied in work. In that time, I switched laptops, newer Node.js versions came out, newer Gatsby versions were released, the ecosystem around MDX and related Node.js libraries also changed. When I finally got some time to write, I cloned by Github repository where I had my blog and began setting it up. And it broke!

I ran into myriad of issues with the setup itself. The Node.js version on my laptop got updated to the latest version (thanks Homebrew!) and it introduced some breaking change in one of the transitive dependencies of the Gatsby packages. Obvious solution? I tried running npm update in an attempt to update all the packages. Did it work? No.

NPM started throwing some peer dependency issues. After hours of Googling around and managing to fix some of them, there were another bunch of issues with packages that relied on native dependencies. Again spending hours of Googling to resolve those issues, some of them required me to downgrade another bunch of packages.

After all of this, NPM again started giving vulnerability errors and, as expected, npm audit fix made the dependency issues even worse. And I could not even get Gatsby up and running on my local machine because of some dependency issue.

As my last attempt, I created a new Gatsby blog from scratch and manually migrated my blog articles one by one. But some of the components that I had configured (like syntax highlighting with custom features like copy button, file title, auto-theming) stopped working. That’s when I gave up.

Issues with Next.js

On some Reddit thread, I read about people moving away from Gatsby and migrating to Next.js. Hoping Next.js would resolve these issues, I opted for it. Again after spending hours in setting up and learning this new platform, I was able to setup the basic blog. Phew! At least this one runs.

My first roadblock was with setting up the syntax highlighting itself. As with every fucking Node.js project, there is distinct learning curve. With Next.js it was this whole compiler-transformer-parser architecture that you need to know to understand (and debug) how to integrate syntax highlighting plugins.

I got PrismJS working for syntax highlighting. But how do I add support for line highlighting? Line numbers? Code block titles? Turns out Next.js needs all of this processing done on server side whereas PrismJS plugins need browser event listeners to work (which are client side).

After spending hours in figuring out how to make it work, I gave up and set out to write my own compiler-transformer for adding necessary class and data- attributes to <pre><code> tags based on my own custom attributes for Markdown code fences. The plan was to prepare the document on server side with all the necessary attributes and then load PrismJS along with its plugins on page load on client side.

Turns out PrismJS would override my custom styles because styles mentioned using <link> tags in Next.js would load after my app stylesheets. And there is no way in Next.js to configure this if you are using app router!

At this point, I practically gave up on these static site generators (rant about these below) and went back to writing HTML files by hand.

Moving to Hugo

After enough frustrations with these static site generators, I decided to give one final attempt. Again on some random Reddit thread, I saw a recommendation for Hugo. I skimmed through the documentation to understand how it works and stumbled upon the whole shortcode concept. This was Hugo’s way to address custom parsing use cases that I wanted to handle.

But my biggest surprise was with the native syntax highlighting support. It had almost everything that I wanted - line numbers, line highlighting, theming. In fact, you can write your own renderer for code blocks and even pass your own custom attributes from your Markdown files! Sweet!

The more I dwelled into the whole shortcode and rendering concepts, I realised how well it was balanced. Just enough abstraction to hide all the underlying complexities on how these are getting handled by Hugo, while giving enough access to do whatever rendering you want to do within the scope of that component.

Syntax highlighting generating inline styles? Convert them into classes and insert your own stylesheets into the application, wherever you want. Do not like the way code blocks are rendered? Create your own renderer. URLs for articles are generated with /posts prefix? Setup permalinks to route articles from the base URL itself. Want to decouple UI elements from third-party themes? Define your own partials.

I managed to setup this blog with every darn customisation that I wanted. And Hugo allowed me to do it without the hassle of going through the internal code, rummaging through Github issues, dealing with dependency hell.

In fact all it took me to add support for code block titles was to define a renderer,

layouts/_default/_markup/render-codeblock.html
1
2
3
4
5
6
7
8
<figure class="code-block">
  {{- with .Attributes.title }}
  <figcaption class="chroma hl"><code>{{ . }}</code></figcaption>
  {{- end }}

  {{ $result := transform.HighlightCodeBlock . }}
  {{ $result.Wrapped }}
</figure>

and then specify the title using custom attribute in Markdown,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
## Some post

This is an example of a code block with title

```go {title="hello.go"}
import "fmt"

func main() {
  fmt.Println("Hello, world")
}
```

It did take some time for me to set this up, but eventually it worked as expected. Customisation was so easy that I ended up creating my own custom minimalist theme.

Conclusion

Hopefully Hugo works just as it worked now in the long term. I do not expect any dependency issues like I had those with Node.js based static site generators. Hugo community and contributors feel a bit more matured and seem to understand the vision. Fingers crossed 🤞


Rant about Node.js ecosystem

Every time I go back to Node.js projects, I spend the first couple of hours just to resolve dependency issues. And if it’s a new project, then the same time is spent unlearning the previous ways of doing things and learning the newer concepts that got introduced in the 2 weeks since I turned my back on Node.js ecosystem.

Each time a dependency is updated, it breaks something else. Some transitive dependency 3 levels deep conflicts with another transitive dependency of some other dependency.

And why does this happen in the first place? Because someone took DRY principle too seriously and created packages for every fucking thing! There is a package for checking whether a number is odd or not! Seriously?! You need a whole package for a simple % 2 operation?

While we can say these developers are obsessed with DRY, I feel Node.js developers also suffer from Not Invented Here syndrome. Too many frameworks trying to do the exact same thing, but in their own ways and with their own ecosystems of plugins and utilities. Plugins for one are not compatible with others. So developers extract out the common bits out of these plugins and then write wrappers for each of these frameworks.

And it feels like these frameworks and libraries are maintained by a bunch of college nerds, who like to create a few fancy projects in their free time, do things as per what they currently feel is the right thing, have very short term vision for their projects, and give no care for the existing users. Take a look at React Router. Its core API changed with every single version that came out. With each React Router release, you had to redo the whole setup, install additional dependencies, uproot it from your project and re-implement it from scratch. And I can’t stick with the same version because it is no longer supports newer React versions, and NPM screams of vulnerability issues because some transitive dependency was a crypto mining malware in disguise.

Some of these frameworks are so vast and complicated, it takes a lot of time read through the documentation and understand how to do even the most basic things. For someone getting into Node.js ecosystem for the first time and not used to navigating their way through the documentation, the dependency issues, the setup and building part, it is an uphill task even to get started. I myself realised how much I relied on my own past experiences in dealing with these issues.

I do not buy the unnecessary complexity that comes due to excessive modularity. While setting up Next.js, I tried to get a grip of the whole unified framework. After spending a few hours, I had to remind myself that I set out to write a blog article and not write a whole fucking parser from scratch! This reeks of immature-ish abstractions and premature design optimisation.

And then there are flavours of the language. Your project is in Typescript? Too bad the dependency you wanted is written in Javascript and has not exposed the type definitions. So, screw you.


Sorry, I do not have that much of free time. I’ve a job and I cannot spend 6 hours every week just to make a darn blog website up and running without issues. IMO languages like Node.js are better suited for quick one-off short term projects. For anything that demands stability and long term development ease, move out to other ecosystems.


comments powered by Disqus