How I made my personal site
Content
This site is first and foremost built around content. I have no formal structure or schedule to my writing. The only general rule of thumb I follow is to lean on the side of writing more, even the insignificant. The getting things done framework talks about getting things out of your head and onto paper, and I’ve found that to be very helpful for keeping my thoughts clear; both as an software engineer who has to keep a lot in brain ram, and as someone with ADHD. The vast majority of my creative writing is done on my phone while I’m on the move; on the bus/train, while walking, or while waiting in line. I tend to do my best thinking while I’m in motion. I do all my writing in a mix of Google Keep and Notion. I primarily use Google Keep out of long-term habit. It was the first note-taking app I got used to all the way back in high school on an old samsung galaxy phone. I discovered Notion in 2017, but for a while the mobile app was fairly complicated and slow on my android phones, which created a lot of friction for me as a largely impulse-driven writer. Nowaday’s though I have a modern iPhone and the Notion app has come a long way, so this may change in the future All of the content you see on this site comes directly from my Notion. I consider it my reliable long-term knowledge archive. My Notion is extremely simple, I just use pages and links mainly, no databases or anything of the sort. You can definitely over-organize your Notion if you’re not careful (I have many times), so keeping the structure flat and simple helps me focus more on the content than the tool.
Design
Overall I strive for a radically content-first design. My goal is to have a site appearance and structure that feels so natural you don’t even think about it.
This means:
- Simple, conventional site structure. Most people already have a muscle-memory intuition of how to navigate pages on the internet. For example, navigation links are usually at the top right of the page. Keeping navigation natural removes mental friction while jumping around
- Natural, legible, unobtrusive fonts and colors. My color palette and type scale come from Radix, which I highly recommend for almost any kind of UI work, not just a blog. I use a neutral palette out of personal preference. What’s nice about Radix colors is that they include both a light and dark variant, ensuring my pages look good in light or dark mode. I also use system fonts rather than a custom font. This is mainly to improve page responsiveness and prevent content-layout shift. However for other projects I love Inter.
- Ruthless emphasis on performance. Nothing grinds my gears more than a great blog article that takes +3 seconds to load on my phone, or stops working on the subway, or loads the content but the buttons don’t work yet. The engineering section goes into more details on how I accomplish this, but it informs the visual and interaction design as well. Visual complexity is kept to a minimum, which reduces the amount of essential CSS and JS assets that need to be loaded. Site layout is consistent across pages, which reduces the amount of bytes sent over the network when fetching new pages. All navigation is expressed though links, which means navigation works immediately. All this leads to a reading experience that feels snappy and lightweight.
- Readable, inspectable markup, styles, and scripts. This is my one exception to the above performance rule. As a web developer I love inspecting sites to learn how they work. But in the modern web landscape this can get really convoluted. So I keep all assets un-minified, unbundled, and uncompiled. You can open devtools right now and play around with the styles and logic if you like.
Engineering
This site is currently built with Deno, Hono, and (mostly) vanilla JS; and is hosted on Fly. I’ve had this site for +6 years, and it’s gone through 3 major revisions. The first version was built as a static Gastby site. ~3 years later I then rebuilt it in Next 13. Then last year I decided to rebuild it again from scratch with the goal of cutting out all the non-essential dependencies, and radically reducing the number of moving parts involved in maintaining the site. The current stack is built around longevity and simplicity. I want this website to last +20 years, and that puts constraints on the degree of operational complexity I’m willing to accept.
Deno
While not for everyone, I prefer using Deno over Node, mainly because it’s just a nicer, simpler to operate version of Node. It lets me use Typescript without needing to maintain a compilation pipeline, which reduces my maintenance burden. Deno also has an (albeit unstable) dedicated KV store and cron scheduler built-in, which means I don’t need to ship an external store like Redis or Postgres for simple projects like this one.
Hono
Hono is my HTTP request handler of choice. It’s simple, conventional, and relatively quick. I especially like its dedicated JSX support, which I use extensively for writing my HTML templates
Preact Signals
For client-side scripting I use vanilla web components with a small signal-backed base class. The base class handles the reactive logic of updating the HTML when a web components internal state changes. It’s similar to Lit but a bit lighter, and uses selectors instead of HTML templates. Because I don’t use a build pipeline, the custom elements I need for each page are included via a <script type=”module”>
tag.
Turbo (Hotwire)
I use Turbo as a small client-side, drop-in performance improvement. Turbo let’s page changes use the same browser process, which helps the browsing experience feel more snappy. Since I use the same CSS and JS globally, using Turbo also helps prevent page changes from refetching assets without even needing to worry about caching. The result is that each page load after the first one only loads new HTML, which is usually <50Kb total. At present I don’t use any sort of link prefetching, mainly just because the site already felt fast enough for my liking, but this may be something I explore in the future.
Fly
Fly is a simple, Docker-based hosting service. I mainly use them because their platform doesn’t have many moving pieces, and I can host a tiny server for free without any real headache. I originally wanted to host this site on an edge network like Deno Deploy or Cloudflare Pages, but the former had some serious responsiveness issues, and the later would require me to bundle my server-side code, which I didn’t want to worry about. With Fly my server works basically 1:1 the same when run locally or hosted, which keeps complexity low and helps me resolve production issues more easily. Since Fly is docker-based I can also migrate to a new provider if needed in the future without needing to re-engineer how my site functions.
How it all works
When the server is started, it will automatically download all the Notion pages I keep within a special top-level page called “blog”. Each page is downloaded, converted from its proprietary block format into Markdown, and then converted again into static HTML. These HTML strings are then cached in Deno KV (which is backed by SQLite when run locally as I’m doing). I cache all my content in a local KV store rather than fetch from Notion at request time mainly as a performance optimization as Notion response times can be quite slow at times, and also has some aggressive rate limiting. With the KV cache, responses can usually be served in <10ms.
When a specific page is requested, a lookup is made in KV, and if there is a match the HTML will be returned to the client. In essence, this is sort of like SSG, just with a smaller, more domain-specific pipeline.
To keep pages current, I use Deno’s cron API to run a “sync” operation every night at midnight. Any that have been updated within the last 24 hours will be refreshed. Since I only rarely write content and none are time-sensitive, I’m fine with content being at most 1-day out of date.
All pages are rendered server-side by Hono upon request and sent to the client as static HTML. All my client-side CSS and JavaScript are included via links and scripts in the <head>
tag. I use vanilla, unprocessed CSS and javascript. This isn’t necessarily the apex of developer experience, but it’s better than you’d think with a little elbow grease. And since my site is mostly content anyway I can get away with minimal client-side dependencies.