You probably never saw my old site. It was short lived. I was proud of it when I built it but I soon found it less than ideal. I think every programmer looks back on code they've written in the past with a little bit of embarrassment and disdain. I think this is healthy. It means I'm growing and learning, and it means that my next ideas will be better than my last as long as I apply myself to understanding what I can do better. To me, my old site represents naive creativity, impracticality, and realizations.
My old site was the first site I built with Next.js. I loved (and still love) the simplicity of Next.js compared to Webpack + Express. It's incredibly easy to build a well-functioning site with React + Next. No need to Google around for Webpack boilerplate (or, sanity forbid, memorize it). No need to set up routes or programmatic routing; it's all built into Next.
But I wanted my site to be more than just an easy React project running on a do-it-all framework. At the time, I felt a need for my site to make a statement about my ingenuity as a programmer. I guess I still feel this way, but I wanted to build "the magic" myself. Enter FinderUI.1
Trees are everywhere
The web is a complex filesystem and our browsers are like Finder or Ranger. We access files in specific locations based on paths that represent the addresses of those files and download them to our browsers for easy viewing. Likewise, a website can be thought of as a simple filesystem and file browser rolled into one. The server code contains the logic that defines the structure of the site, and the nav bar contains links to pages of content served on demand. Therefore, my old site was designed and built around the concept of a file browser.
It's common to organize filesystems as trees, with directories as subtrees and files as nodes. Therefore, websites can also be conceptualized as trees. I know I'm stating the obvious, but some people may have taken this for granted and failed to make the connection between public filesystems on webservers and formal tree data structures.
It was this realization that led me to experiment with creating a React component that takes a JSON object as input and spits a website out with the same structure as the object. Here's what that looks like in theory:
I wasn't sure that this was a novel idea, but it flowed from personal discovery so I was willing to chase the rabbit down the hole. I created a React component that, given a JS object containing references to other components, would render a React component at the end of a given key path. I called this FinderUI because, visually, it could easily be styled to resemble MacOS Finder. The code is here.
I create life (and I destroy it)
FinderUI worked by receiving a configuration object2, assigning an
id to each node in the object (for defining key paths for easy traversal), and using reducer hooks to manage state and control which component was rendered at a given time. It contained a fixed sidebar with "panes" of links leading to either "files" (pages) or "directories" (folders of pages). Clicking on a link to a file would display the contents of that file in the main portion of the viewport, while clicking on a link to a directory would render a new pane of links to the files in that directory. The result was a web application that basically worked like a React-powered GUI file browser.
In practice, this component provided some advantages. For one, I was able to easily hook into view changes on my site, so I could use the History API to update the browser state upon navigation and also register page views with Google Analytics. This flexibility and configurability made FinderUI feel as robust as it was snappy, since I was able to disable Next.js' filesystem routing and rely solely on FinderUI's lightweight programmatic navigation mechanism and the power of React Async to load dynamic resources on demand. The result performed well, registering straight-As on webpagetest.org and all 100%s on Google Pagespeed Insights.3
I was even able to pass a lambda function called FUILink from FinderUI down to the display components so that client-side navigation could be achieved within FinderUI. Coupled with a simple Express server to handle incoming requests to the server, the site felt full-featured and fast. It was nice to be able to change the structure of the site on a whim, simply by modifying the configuration object, and it felt cool to have built a framework for myself. Though I was relying on Next.js and React for SSR, building, and rendering, I had built "the magic".
FinderUI was far from perfect, however. First of all, it was nothing new or exciting. For each benefit it seemed to provide, there was a better alternative available. First of all, React Router was and is an all-around better programmatic navigation library, with an even lighter package size. Second, I soon realized that the filesystem routing provided by Next.js is far superior to my idea of representing a website's tree structure as a JS object because it keeps things intuitive by default. Third, the built in navigation UI which was meant to emulate Finder just wasn't a strong enough (or a general enough) piece of code to handle use-cases in the wild. The whole thing felt rather experimental and weak.
I was in the middle of writing a documentation website for FinderUI (then renamed to StructUI) when I decided to scrap the project, archive it, and build myself a completely new site. Rather than attempt to build all of "the magic" myself, I wanted to be selective in what "magic" I used from other people and wise in building an effective site. It takes a good programmer to rewrite everything themselves. It takes a greater (and perhaps more humble) programmer to wisely use tools that others have built in the pursuit of getting the job done right.
A new site
Here's a few things I wanted in my new site:
- Easy to add quality content
- Basic enough to allow for creativity
First, minimal. Minimal is hard to define because it can mean a lot of things. One thing it certainly doesn't mean is complexity, so my new site should be a return to "the basics" in comparison to the old site. I also think "minimal" means a lighter site, and the old site was weighed down by the over 100 KB of React boilerplate required to render the site.
Tooling and design
There are countless static site generators out there looking for users. One that caught my eye was 11ty, the self described "simpler static site generator". Eleventy is a simpler and more flexible option with a "BYOB" approach to extended functionality. This fits with my desire to be creative since it gives me the opportunity to add some of my own "magic" to the mix. As it turns out, it also makes it easy to add quality content because I can write custom filters to script away any content massaging or data manipulation that needs to happen at build time. More on that in a bit.
Armed with Eleventy (and Nunjucks), I set about creating my new site. As I considered a design for my site, I knew I wanted to keep it simple like my previous site had been. I especially wanted to keep things simple because it would allow me to more easily keep things HTML, inline my CSS, and keep requests to a minimum. I came across macwright.com, an excellent and simply-designed site built on Jekyll. Tom, the author of that site, wrote a pretty cool post called This page weighs 15kb that certainly influenced me as I decided how to create my site. For example, I saw how well his site reflected one particular paragraph heading that caught my eye: The best optimization is deletion.
Indeed, if you visit Tom's website (and you should), you'll see that my site bears a strong resemblance. Like Tom, I chose to apply very few styles to my HTML in the interest of a more snappy experience. I also happen to think this results in a cleaner and more timeless look; user-agent styles are defaults for a reason. One of my favorite things about the design Tom used (and I shamelessly ripped) is that it's extremely mobile first. There's no need to worry about collapsing sidebars or complex truncation because the site is mostly one column. The only media queries on my site are there to increase the size of tap targets to make things more mobile-friendly.
I'll save you the boring parts: I built my site with Nunjucks templates on 11ty5 and tried to use very little of anything. It worked. My page weights tend to hover in the single to double-digits of kilobytes, and it's a cool feeling. However, past the unnecessary optimizations, there are some things I'm proud of.
Custom filters for inlining SVGs
As you can see, all this function does is read a file at a given path into a string and return that string to the template. This little function removes the need to either request SVG assets on the client or copy-paste SVGs everywhere I want one. Filters are really convenient if you like writing your own code to glue things together, but be warned if you rely on boilerplate: 11ty is pretty minimal, so you'll probably need to think critically about anything you need beyond what you're given and then make it happen yourself.
Scripts for initiating new posts and optimizing images
One of my "few things I wanted in my new site" was "easy to add quality content". JAMstack certainly helped with this by letting me focus on content, but there were still a couple of pain-points:
- Optimizing images was a painful, manual process
- Setting up a new post in "writing" required manually creating files and directories and then copy-pasting some boilerplate
So, I decided to write a couple scripts.
To automate my image optimization, I initially assumed I needed to harness the power of ImageMagick to convert my images into the desired formats (WebP and JPG) and sizes (400px, 600px, and 1000px widths). However, I poked around a little more and discovered bespoke, yet another piece of work by the aforementioned Tom.6
The real power behind bespoke is sharp, a blazing fast image processing library with an intuitive API. Needless to say, writing my own bespoke version of "bespoke" wasn't difficult, and it wasn't long before I had a script to automatically convert any image I needed into the right sizes and formats for my site. What would normally be a painful task is now as simple as running
npm run img and
mv-ing the files to the proper folder.
As for automating "new post" boilerplate, this seemed like a job for Perl. Sure enough, 86 lines of Perl was all it took. Creating the necessary directories for the new post and its assets, assigning the post an index (for ordering), creating the empty file, generating the frontmatter, and copying boilerplate into the file all became a simple matter of
npm run post 'My post name'.
Unless you're reading this on an RSS reader, you're seeing the fruits of my labors. This site is fast. It achieves the perfect scores across the board on Pagespeed Insights and an average speed index of ~400ms according to webpagetest.com. And to make matters even better, I found a way to make use of the ideas that spawned my FinderUI/StructUI adventure after all: treebox.
As it turns out, rendering a piece of markup based on a path within a tree structure is still kind of a cool idea. It's also kind of useful. In fact, I'll be using this concept to create a sort of "lightbox" to show off demos and descriptions of my personal projects in the future. The React ecosystem may not need another attempt at programmatic routing and rendering, but those who stick to vanilla JS just might find it charming enough to use it. Here's a CodePen example if you're interested.
Hopefully you found this post at least readable and at most somewhat inspiring. I encourage you to work on finding the right tools for what you're trying to accomplish, but don't let the fear of creating your own "FinderUI" keep you from learning something new. Stoke levels are important to workers of all industries, and the most effective professionals are always the best learners. Stay thirsty, my friends.