How I Made This Blog
Including Steps and Tools
June 12, 2026 | 7 minutes
Everything I learned, helped me realize: man, I probably could have just used Hugo to do all this. But honestly, I had a lot of fun doing this.
But I’m a former web developer. I already know how half of all this works. And I’m a minimalist. I want to make it all myself, so it’s as small as I want, dangit.
But for those like me, here’s how I did it.
Markdown to HTML
I wanted to include my content from my Beehiiv newsletter, at least any still relevant (i.e not all the “hey I just uploaded” posts). I didn’t want to just download the HTML, as most reactive website’s HTML is messy (even the non-reactive HTML). I’m a big fan of plain text and Markdown, so that’s what I converted them to.
I initially found a nice “Beehiiv to markdown” converter online that did a great job. But I ran it at the beginning of this year, thinking I wouldn’t post any more, and deleted the scripts. Unfortunately, the owner took the script off GitHub (may have been forced by Beehiiv to take it off, now that I think about it). So I resorted to an online free “HTML to Markdown” converters for the latest ones. Manual work still had to be done, including checking for special characters that don’t convert to plain text correctly.
Archived HTML to Markdown, done. Now I needed something to
convert my Markdown back to HTML. So I just used pandoc. I used it briefly before,
but honestly I’m impressed how powerful yet simple it is. All I
added was some basic metadata, give a template html
file, and it was good to go. Really love it.
I could just convert each markdown file and slap an
.html on the end, like so:
build/
|------ sequels_bioshock.html
|------ making_friends_online.html
But I personally like seeing URLs without the file extension, so I changed it to this:
build/
|- sequels_bioshock/
|- index.html
|------ making_friends_online/
|- index.html
Many thanks to pandoc for doing the hard work of conversion.
RSS, by hand
This partially inspired me to move off Beehiiv. They hide the RSS option for creators (newsletters don’t automatically get an RSS, you tell it to generate one), and they recently hid it from the default newsletter page.
I probably didn’t need to manually script this. Just find a library or framework to handle RSS for you. Most frameworks now have the option for it.
But I will say that it wasn’t as frightening as I thought. They’re pretty straightforward, especially my articles already have the related metadata. Here’s some Python pseudocode, for anyone interested (I can publish a full template, if I get enough feedback):
from xml.etree import ElementTree as ET
def build_rss(articles, channel_config):
rss_xml = ET.Element('rss')
# set XML version and formats, such as atom support
channel = ET.SubElement(rss_xml, 'channel')
# set RSS channel info: title, description, link,
# image, language, latest article's publish date
article_metadata = []
# most RSS feeds show only the latest, say, 20 articles.
# That way it doesn't take forever to load,
# especially if the *entire* article content is in the RSS
for article in article_metadata[:20]:
item = ET.SubElement(channel, 'item')
# set the title, link, description, publish date
# set a guid for easier tracking in RSS,
# just in case you change URLs at some point
return rss_xmlI don’t have many images in these blog posts, but for those that do, you could probably do some smart scripting to grab the right image from each page (or just the first image) and mark that as the RSS item’s image.
Styling the Site
In the words of Supa Hot Fire, “I’m not a rapper
graphic designer.” This is when I spent a day trying to convert
my existing website so far to just Hugo. That way I could just
use a Hugo theme. But I quickly learned that it’s easier to
start Hugo from scratch than to convert.
So I just stayed with my manually built website. I initially used my links page’s CSS as a starting point, which honestly got it pretty far. For help, I consulted UX documentation online. I made the articles skinnier so it was easier to read — no head-turning on my wide monitor.
I asked the Fediverse for visual inspiration. And y’all have some cool-looking sites! Go see people’s websites; don’t just read from your RSS reader. It ended up inspiring a lot of functionality for my website, which I’ll get to later.
The “paper” and monochrome themes were the most appealing to
me. For some reason, I started thinking of the Moon, eclipses,
and lunar visuals (maybe because of the recent flight to the
Moon 😛). I played with crescent shapes. You can easily do this
with CSS borders. After further experimenting, I
found myself enjoying gradients with box-shadow. So
I started adding those, and boy oh boy, did it give my site a
facelift.
Nothing is so inherently aesthetic and beautiful than visual consistency. Humans are bad at it, but thankfully code often can be. Make a bunch of rules somewhat related to each other, and then just follow them.
I’ll get into accessibility later, but that played a big part here. Thankfully, a mostly-monochrome paper style made it easy for myself. I may add stronger colors later, but that’s for another time.
My (50cc) Search Engine
Again, you can do this easily in something like Hugo. While I don’t have too many articles yet — about a couple dozen migrated from Beehiiv — I figured it would be helpful for those only interested in certain topics. I can be a bit all over the place. In the words of Ye, “I love this guy’s Blender stuff. What the heck does he know about Kelsea Ballerini?”
Everything I learned, I learned from “Building a Simple Search Engine That Actually Works”. Great article breaking down the mechanics of search engines into pseudocode-ready explanations. But in short, here’s the steps:
- Tokenize the data. Scrape titles, descriptions, and content and count individual words and even parts of words (to handle typos and give useful results while the user is still typing). This mostly decides how big your search index is.
- For each article, count and “score” each token it has, based on how important the words are (for example, if it’s in the title, I score the token higher). This is all stored in a database, cache file, however you want. If it’s going to be downloaded in the user’s web client, keep it small, ideally less than a couple megabytes (I did this by saving articles by index instead of by name).
- When the user types something to search, tokenize the text in the same way you tokenized the data.
- Check if each of the user’s tokens are in the database. Retrieve the articles and scores associated with the tokens.
- Sum up the scores for each article retrieved. The highest scored articles are ranked first.
My tokenization is done offline, in my build script, all put into a cache file. This cache gets uploaded to the static site. Then my webpage Javascript loads the cache file in, and checks for the tokens as the user searches.
The search is crazy fast, even with 10,000+ tokens. Not bad for <1 MB cache file.
As for how I handle the search listing in the browser, the Javascript just re-sorts the article HTML elements. It’s a little hacky, but it works, and I can easily re-sort it back to its original order if the user clears the search bar.
Other Necessities
Most of these I found while browsing others’ blogs, so I’ll document here:
- Accessibility
- Keep text easy to read. Slightly large font size helps, but so does contrast. I credit WCAG Contrast checker where I could color pick my text and background to ensure it followed contrast guidelines, even for people with different vision. This ensured my gradients weren’t clashing with the text. For any worriers: so long as you use near-white and near-black between your text and background, you should be fine. Thick outlines can mitigate any edge cases.
- Add alt text. For non-image elements, you can use the
aria-labelHTML attribute. - Light, dark, and system themes. I initially just did system theme, but some people like to force the theme to be the opposite for some apps. It was a little weird, but I got it working and nearly all CSS. The last bit of Javascript is to store the value in local storage, so people don’t have to set it every time (and unlike cookies, don’t get sent to the server by default).
- use
titleandmetatags in yourheadHTML! Both are useful when sharing links to the site and articles. A common naming scheme is<article title> | <website title>.
- “Back to Top” button. Some blogs don’t need this if the posts are short. But I can get long-winded, so I knew adding one would be helpful. All in CSS, thank goodness. And that it fades in after scrolling down a bit? *Chef’s kiss* (you just tried it, didn’t you?)
- A Terms of Service and/or Privacy Policy. Privacy laws are
becoming more of a thing, and in general users are more aware of
how websites can track you. Read up on it; don’t just
find tips. Know what is required for the content you want to
share. Be careful about anything embedded: fonts,
images, video players, gifs. Some add cookies, even if it’s just
a link. If you need embedded content like YouTube players, then
just disclose that (
youtube-nocookiedoes minimize cookies and tracking, but not all of it). I converted my YouTube embeds to links (I may add PeerTube embeds later), and used heypster for a few gifs I could find. - Obfuscate your contact info so bots don’t
grab it. Never put your email, contiguously, in plain
text. There’s many ways to make it difficult, including
conventions like
name (at) email (dot) com. But another more effective method is to add contact info indirectly via Javascript or a separate non-web file. I put a “dummy” email in the HTML, and replace parts of it with a real email. That way the email is never in plain text, all together, in either file. Bots rarely load Javascript because it slows them down, and even if they do, they rarely process HTML with styling and Javascript all together. - Error handling, particularly if Javascript is disabled by
the user or fails to load. It’s rare, but some visitors disable
it, or some aggressive ad blocker extensions may. In my case, I
use
noscriptto show that the search bar is disabled unless they enable javascript.