Some time ago, I've decided to start a blog, and contrary to what my engineering background would make me want to do, I wasn't really thinking actively about which tech to use for it.
Last weekend I decided to act on it and started prototyping some stuff. Over those two and a half days, in a coding frenzy, I had a lot of fun - but I also ended with a pretty satisfying end result. And it also went against a lot of things that modern web development advocates you should do, which provided me with great enjoyment. So let's start.
The first thing I knew I wanted was something CMS-like, but lightweight, both in terms of performance and ease of use. This meant WordPress and Ghost were definitely out of the picture. They are just too much overhead if you just want a website with a landing page and few blog post subpages.
Also, even with Docker, the server setup for them always felt complicated to me. There's too much config and orchestration to get them to run properly. I had to set them up a few times, it always felt very cumbersome, at least for a non-devops person like me.
Related to that, I also wanted to be able to run the blog from my servers. I have a VPS I use for my other projects, so I'd prefer that over using some hosting which I'd have to pay for separately. And the free ones are the worst, as they usually let you in nicely, and then extort you when you decide to add a custom domain or SSL certificate.
And then there's the most important requirement - since I'm writing the actual blog posts in markdown format, I wanted to be able to publish them easily to the blog. I wanted to keep those files in one place, instead of having to copy-paste them in some web UI.
Does that mean that maybe one of the various "static site generators" like Jekyll or Gatsby would be the best fit for my use case? I definitely like that approach conceptually and appreciate the fact that they're taking some market share from Bloaty McBloatface CMSs like WordPress or Joomla, but after trying it, they didn't feel as simple as they advertise themselves to be.
For example, why do Gatsby setup docs mention React and GraphQL? And they also explicitly require you to downgrade your Node version to 10, uhmmm u wot m8? I thought this was something for non-programmers to get the site running without any code. I just want to view my markdown articles from browser, why do I need to care about any of that?
Plot twist: in the end I ended up creating a "static site generator" very similar to this.
Ok, so my first idea was this - since I didn't want a CMS, or in fact any server side code, I initially conceived it as a completely client-side solution. There was a catch-all entry point, let's call it index.html, which would generate the requested page on client-side, based on the request URL.
For example, a call to myblog.com/ would asynchronously load some landing.html subpage and embed it in itself, while a call to myblog.com/blog/some-post-from-my-blog would load some-post-from-my-blog.md markdown file, convert it, and embed in itself like a regular HTML file.
So there I was, backend-less, thinking how innovative I am, displaying markdown files like they're HTMLs. After some time I realized I was basically reinventing SPAs. And I only figured it out when I had this great idea about "hey why do we even need html, lets build a small dsl to create the ui".
If I wanted to use React, I would've used it from beginning. But I didn't want to, as we're aiming for simplicity here, and adding a UI library again seemed like overkill for what I need.
It was about this time that I stumbled upon a tiny website called 1MB club. Despite their somewhat aggressively worded opinions about modern web, the thing resonated with me. I took a look at some of the examples and got really impressed in how much content people can put on a page weighing under 20 kb.
I also liked the aesthetics of those pages, they're almost brutalist by nature, so it feels really refreshing to see parts of the internet which deviate from mainstream design patterns.
At this point I knew what I had to do. I had to stray off the conventional JS path and try to think of a system which would allow me to create minimal pages without any fluff, just focusing on content itself.
The solution that I now had in mind was to move the generation to "backend" - but not really a classic web service backend, more like a local script which will take some of the templates (HTML and Markdown files), convert and stitch them into static pages.
I figured that having even a simple server-side code would add some overhead, and in this minimalistic endeavour I wanted to avoid as much of it. So the thing ended up looking like a simple SSG, which was immensely liberating.
I still like to use JS for my general scripting purposes. Yes, I've seen the memes, I know that it's probably the worst invention humans have ever created, but having worked with it professionally for few years, I've gotten to know its quirks well enough to not cause me headaches. I wouldn't want to use it on a big production system (or anything weakly typed), but it's my first pick when hacking small projects like this one.
So that's what I did - the behavior was the same as in my fake-SPA version, with the difference that the HTML files were built with the script, and then served statically.
The project was structured in two layers - the generator part which contained the plumbing code, and pages which contained the actual pages. Each page had a html and js file, which generator would read, combine based on the logic in js file and just output as a static HTML file.
For a page, you can just use plain HTML, and it's gonna show it. Or you could add a js file that would manipulate it in a certain way. For example, one example of custom behavior is to generate a list of all blog posts, or the blog posts themselves, like this one. The js file for that certain webpage scans the contents folder, enumerates the Markdown files to create a list page, then takes each one and applies a MarkdownToHtmlConverter to generate the HTMLs.
It might sound complicated when outlined as above, but the implementation was dead simple. There is no complicated term or pattern mentioned in the whole project, most of the logic was implemented using these few lads:
There are no reducers or sagas, no switchMapLatest, no XMLNodeNavigator, just file IO and string replacement. It's funny how many modern web development problems are caused by the complexity of the tools we ourselves introduced to make our development lives easier. Not having anything eliminates a whole bunch of problems in the start.
That MarkdownToHtmlConverter I mentioned before - I tried to write it myself and had it ready in like an hour or so. I'm not flexing, it's just Pareto principle - you can get first 80% of something done in 20% of time. These are the tags my converter works with:
If I were to implement the full spec, it would take a lot more time. It's the last 20% of features that are hard, which are the causes of the bloat which you possibly don't need. If you're writing something tailor-made for your exact purpose, you can go really fast and not break things.
(also, I'm posting code snippets as images because multiline code tag is in those last 20% of features. sorry)
I felt absolute liberation implementing all this, seeing how dumb and simple it can be when compared to rendering a website the conventional way (either client or server side), and when it's tailor-made for your exact purpose without adding any bloat.
Modern software development with big teams and projects feels bereft of joys like these, so it's good to do similar things here and there to remind yourself on why you started liking this damned job in the first place.
Now I'm thinking - maybe you should try to reinvent React for your hobby project. You'd be surprised how quickly you can get to the first 80%.
I eventually got the JSX idea back on, but with the crucial and fitting behavior change - instead of creating actual DOM elements, they are just strings:
Really dumb, right? Their composition works pretty well using just string interpolation:
You can easily create bigger components out of them and compose them the same way. I won't even write HTML anymore in this project (except for the component definitions in HtmlComponents.ts file).
Generated outputs are pretty small. Base size of those HTMLs is around 1-2 kb, which will increase depending on the contents of each page. I expect blog posts to be around 20 kb, which is really good when compared to average page size in 2020, which seems around 2 MB. I could fit 💯 of my pages in that amount.
Maybe I could even copy this guy and concatenate everything into a single page?
But let's try to reduce the size even more by minifying the outputs. Which minifier from npm should we use? html-minifier? uglify-js? tinyficator-css?
*WRONG!* That was a trick question, it's only string.replace() for us now:
Replacing whitespace and just chucking everything into </style> tag works, but it's again only a "80%" solution, as some CSS properties need to have whitespace in them. So I can't margin: 0 auto;, forcing me to get creative and google a different way to center a div (JK - I'm a flex guy).
And for HTML, it's a bit worse, so I gave up on that idea immediately. That language has a weird relationship with whitespace anyway, so I didn't want to intrude too much.
Also, all images on the blog have a loading="lazy" attribute which should be well supported in the browsers of today. What's cool is that it loads the image when it "reaches a calculated distance from the viewport", just in time to be ready when it actually enters your viewport.
To deploy the app, I'm again using the dumbest possible solution - the "just copy the files to server bro" method. I'm not drag and dropping files through Filezilla though, there's a script instead:
npm run build && scp -r build/* firstname.lastname@example.org:/home/mbrizic/hosting/blog
Instead of dealing with Jenkins configuration, how cool it is to have a one-liner script to deploy the app to the server instantly? Very.
The server is a $5 Ubuntu VPS from DigitalOcean, and this website is sitting behind a Nginx reverse-proxy with gzip on;. I wanted to add a Cloudflare cache in front of it, but I wont, as having less things keeps you from having more problems.
So there you have it. As you can see, I really enjoyed working on this project, as well as writing this post, so hope you did so as well.
Obviously, on serious real world projects you might not be able to cowboy your way through it this way. But it's an interesting exercise to try and see how much you can accomplish without subscribing to the latest JS framework craze, and which benefits you would get by NOT using any of it.