I've never been a fan of modern website architecture. Websites these days are so bloated with JavaScript and suffer bad performance on low-end devices. It's because of this that I'm a big supporter of minimal JS websites. And don't get me started on JS frameworks like jQuery.
Every website I've built in the past was made exclusively with HTML, CSS—and on rare occasions—a bit of JavaScript as a treat. You'd be surprised how much animation you can do with just CSS selectors!
But if that was all I did then I wouldn't be talking about it much, would I? You could just press F12 to check out my website's page source. No, I did something different this time.
But before I can talk about my blog, I first have to talk about1 how I built my website.
When I opened up the text editor to start making my website, I had two choices: stick with what I'm familiar with and use plain HTML and CSS, or learn something new.
The world of web design is full of frameworks and libraries to do anything you need. Svelte, Vue, Angular, React, you name it. Years ago I tried Angular once, but I didn't like the way it forced me to structure my code. I ended up making the assumption that other frameworks were the same.
So when I was checking out a React tutorial on W3Schools, I was surprised to see how simple and agnostic it was. It wasn't a framework, but a view library, and it had one task: quickly and efficiently update the browser's DOM by reacting to changes.
Here's a short briefer of how it works. React keeps a lightweight virtual DOM which is a copy of the browser DOM. Any time there is a change to the virtual DOM, it can pinpoint what changed and reflect that in the browser DOM.
While this is designed for large websites like Facebook and Twitter X to keep track of everything without grinding to a halt, it includes two other paradigms that I personally find invaluable.
The first is components. To be able to keep a lightweight shell for the virtual DOM, it needs to be broken up into discrete components. A page might have components for the profile, search bar, and feed, and the feed can contain post components, which could be further broken up into like and share button components.
<!-- Page -->
<SearchBar />
<Profile />
<Feed />
<!-- Feed component -->
<div class="Feed">
<Post msg={ "Hello" } />
<Post msg={ "world" } />
<Post msg={ "!" } />
</div>
<!-- Post component -->
<div class="Post">
<p>{ props.msg }</p>
<div>
<LikeButton />
<ShareButton />
</div>
</div>
Now, whenever you need to update a like counter in real time, it's easy for React to see what changed and efficiently update the browser DOM.
These components are modular, meaning I can reuse them in multiple places like functions in programming! They are also data-driven, which means I can pass in data for it to render.
For example, here is the JSON object for one of the game cards on my home page.
{
name: "Bunpacking",
url: "https://chailotl.itch.io/bunpacking",
desc: [
"You're a postbunny making deliveries around the island.",
"I made this with Kett in 72 hours for Ludum Dare 53. I did the programming and they did all the assets."
],
thumb: "https://img.itch.zone/aW1nLzEyMDQ5MTI3LnBuZw==/315x250%23c/oertQ5.png",
date: "2023-05-01",
links: [
{
name: "Ludum Dare entry",
url: "https://ldjam.com/events/ludum-dare/53/bunpacking"
},
{
name: "source code",
url: "https://github.com/Chailotl/ludum-dare-53"
}
]
}
React takes this and puts it into a ShowcaseCard
component I made and it renders out like this! Pretty slick, huh?
If you've ever created HTML elements in JavaScript, you'll know it isn't pretty. And if you have to create a lot of nested tags, it's best to use something like template tags.
// Clunky!
const myElement = document.createElement("h1");
myElement.innerText = "Hello world!";
But all is not lost for there is something better. Much Better.
Introducing… JavaScript XML (JSX)!
// So cool!
const myElement = <h1>Hello world!</h1>;
That's right, inline HTML. You can even do inline JavaScript expressions too!
const text = "Hello world!";
const myElement = <h1>{text}</h1>;
While this is just pretty syntactic sugar that compiles down to plain JavaScript, it makes programming HTML fun and easy!
If you're privy to JS, you'll know that these fancy React components and JSX won't "just work" in JS because it doesn't natively understand them. You'll need a transpiler to turn it into valid JS, just like you would with TypeScript. That's where Create React App (CRA) comes in.
To make jumpstarting a React app quick and easy, the team behind React created CRA. Not only does it handle the transpiling, it also doubles as your local development web server. Any changes you do in the code will be instantly reflected on the webpage, making prototyping a breeze.
There is more to what React can do, but the gist of it is that my website is built on React and its wonderful feature set.
"So your blog is built with React? Case closed then—" Not so fast! While I could have used just React and called it a day, there were some features I was looking for in my blog, and some problems I needed to overcome.
Let's go over the first point.
CRA is an awesome way to quickly get into making your first React application, but it has one glaring problem: everything is rendered client-side.
When you open the React page, your browser is sent an empty HTML document, and the scripts which are included with it then build the page. All in your browser.
What this means is that for a short amount of time you will see nothing on the page until React renders it. This is also an unnecessary load on your browser. It gets worse if you have JavaScript disabled: you'll see never see anything!
While there are some clever solutions to this, such as pre-rendering the HTML then hydrating2 it, I wasn't going to be happy with this ultimately.3
There are two methods besides client-side rendering, and those are server-side rendering (SSR) and static site generation (SSG).
SSR as the name implies is having the server render the HTML each time a browser requests it, and SSG turns the interactive React app into static HTML files you can upload to any webhost.
I chose to go with the SSR route with Next.js, a framework built on React. Next.js has a lot of nice features, such as caching rendered pages, and automatically optimizing images, fonts, and scripts for even speedier page loads.
The main thing I wanted for my blog is to be able to write posts in markdown like so.
# A heading
Normal text, *italic text*, [a link](https://google.com), and some **bold text**!
Lucky for me, getting this to work seamlessly with Next.js was a chinch. I just had to install @next/mdx
, configure it in next.config.js
, and then I could import markdown files like a React component!4
Because this is all rendered server-side, I can do stuff like grabbing all posts with a certain tag or category without making the client do a bunch of network requests for each and every markdown file.
The way the markdown importer works is by leveraging the unified ecosystem. It's a project that transforms content with abstract syntax trees (ASTs), where remark handles markdown and rehype handles HMTL.
In layman's terms, it can translate markdown into a "shared language", and then translate that into HTML.
You may think this is over-engineered, but doing it this way provides a powerful ability: using remark and rehype plugins to do additional transformations between markdown and HTML.
One of these plugins is remark-gfm
, and it adds support for GitHub-flavor markdown. It lets me do strikethroughs, footnotes5, tables, and task lists.
Name | Height | Weight | Color |
---|---|---|---|
Chailotl | 3 ft | 35 lbs | lavender |
Here's a list of the remark and rehype plugins I am currently using.
remark-breaks
adds <br>
to preserve line breaksremark-frontmatter
is something I will be talking about later!remark-emoji
turns :shortcodes:
into emojisrehype-twemojify
turns emojis into Twemojisrehype-highlight
adds CSS classes to code blocks so I can give them syntax highlightingrehype-slug
adds id's to headersrehype-autolink-headings
uses those id's to add links to headingsWhen I was researching how to import markdown, I stumbled across something magical. It's called MDX, and it lets me use JSX in my markdown. Oh, that wavy text I used just now? That's a React component in action!
In theory I could write any component I want to do whatever I need and import it into my blog posts. Could be charts, interactive elements, YouTube embeds, you name it!
Maybe I'll write a future blog post sharing all the fun components I came up with.
Two months ago Kett introduced me to an awesome writing app called Obsidian. Its notes are stored in markdown, and it has a feature called front matter.
In essence, front matter is the note's metadata, and it can be queried in a variety of ways in Obsidian. For example you could have a folder of video game reviews, and the front matter could store information such as the game's name, description, date of review, your rating, etc.
You can add front matter to a note by writing YAML at the top and enclosing it with ---
like so.
---
name: A Hat in Time
description: 'A Hat in Time is a cute-as-heck 3D platformer featuring a little girl who stitches hats for wicked powers! Freely explore giant worlds and recover Time Pieces to travel to new heights!'
genres: ['3D Platformer', 'Collectathon', 'Adventure']
myRating: 9
wouldRecommend: true
reviewDate: 2022-08-23
---
And I thought to myself, I could use this for my blog posts! So I did. Here's the first post's front matter as an example.
---
title: Why I want to blog
abstract: As a kid I was enchanted with the idea of blogging so I created many blogs about different topics growing up. Today none of them exist, so what made me want to start another blog?
category: blog
tags: ['personal', 'meta']
publishDate: 2023-09-12
isPublished: true
---
title
and abstract
describe the post when reading from the main pagecategory
decides whether it's located under blogs or devlogstags
lets you search the post by tagpublishDate
lists when the post was published and determines the sorting orderisPublished
determines whether the post is visible on my blogWho knows, I could have unpublished blogs you could read if you just type the right url ;)
What's a blog without an RSS feed? Well, it would still be a blog because I'm not sure if many people still use RSS feeds, but hey, it'll make me happy knowing my blog is complete.
This was actually something I struggled quite a lot with, and for one specific reason. Fetching all the blog posts and reading their front matter to build the feed was easy enough, but turning the markdown into HTML for the feed's content was a lot harder than it should have been. I tried what must've been a dozen different methods, only for all of them to fail one way or another.
The markdown-as-a-React-component had no built-in method to turn it into a string. React had a function to render HTML as a string, but it was not only broken server-side, but didn't even work when I fixed it because of asynchronous bullshit.
I tried a couple of HTML-to-string modules, but none of them worked because of the same async problem.
At one point I just gave up and shoved the raw markdown into the feed, but Kett was quick to notice how janky it looked.
So I resigned and went with the nuclear route of running an entire separate markdown-to-HTML library just to get it working for my feed.
While I could have done that in the first place and saved myself a lot of headache, the reason I wanted to use Next.js to import the markdown and turn it into a string was so that I used the same MDX import pipeline with the same remark/rehype plugin configs. Oh well.
So why go through all the effort of building my own blog stack instead of using a ready-to-use solution like Wordpress or Medium?
For me it was a learning opportunity on what goes into making a blog from scratch. I can then use that knowledge for future projects, especially for when I apply to web dev jobs!
In the end I am happy with how all these pieces fit together, but that doesn't mark the end of my blog's development! There's always more to do, from refining my blog's theme to creating more React components to use in my posts, so stay tuned!