improving the site (2025)

Sun Jul 06 2025

tags: public blog programming featured

previous posts in this entry:

This year, I made two large architectural changes, things I've wanted to do for a long time.

content-presentation separation

The largest architectural change is a clear separation between content and presentation. When I first started this website it was a Jekyll static site, then Eleventy. Down the line I wanted to make it a Obsidian vault as well, and I did, but due to all of this historical baggage it was never quite done right, many vestigial files, etc.

I refactored the Github repo to have a very clear structure: on the root level we have vault and views, where vault is the Obsidian vault (content layer), while views is the presentation layer.

lieu/
├── vault/                           # 📁 CONTENT LAYER
└── views/                           # 🎨 PRESENTATION LAYER
	└── eleventy/                    # Eleventy SSG implementation

Conceptually, I want the vault to be "pure": it has all my notes and thoughts, and doesn't concern itself with presentation. The website is one of many potential views over my vault. Completely separating content and presentation lets me change or add new views if I see fit -- if e.g. Eleventy stops being supported, or I want to build a server-side rendered second brain with full text search over my private posts -- without having to change anything about the vault.

The other advantage is it also lets me back up my Obsidian vault with Obsidian Sync, which means that I can read and draft posts on my phone with the Obsidian app.

persistent, global blob storage

I want my second brain to store not just my written thoughts, but also my memories in the form of PDFs, images, videos, etc. I started putting all these files in a folder called /img inside the root folder, and backing it all up on Git. Of course, this is a bad idea, and very soon I had to explore alternative solutions.

For the past few years I used Git LFS, and maybe it's a skill issue on my part, but it's entirely horrible. I've tried many times to make it work, it just sucks, it's so painful, I eventually decided to rip it out entirely (losing all my old commit history).

So I decided to build a global blob storage service that

  • stores all my data (videos, photos, PDFs, ...)
  • serves it up at a persistent global URL (e.g. files.lieu.gg/img/group_testing/queue.png)
  • allows me to upload data (from blob-service.lieu.gg)
  • automatically captures new data from my phone and desktop (e.g phone screenshots or screen recordings, desktop screenshots, pasted images..)

The first three points are done with Cloudflare R2 and a Cloudflare worker, pretty much all done by Claude. That all works very well. The last one is a work in progress, and requires a bunch of different scripts: for example, Obsidian stores all pasted images in the local folder, so I had to write a hook that intercepts that paste, move the file to a temporary folder, upload it to the R2 bucket, delete the file, etc. For the phone I use Tasker.

downstream cleanups

Lots of smaller cleanups needed, some of which were downstream of the infra change. The collections system / system of filters I had used to categorise posts on the website was simplified and rewritten. I needed to remove layout:base from the front matter of all my Markdown files (because layout:base is a purely presentation consideration).

I migrated all /img/... paths to https://files.lieu.gg/... paths. This would have been painful before but Claude Code did a really good job at it, it wrote a bunch of not-great regexes, but if you check its work and tell it it's missed a bunch of files, it can eventually do it

claude code is incredible

This is the first improvement on my site that was AI-assisted! I guess this must mark a sea change of some sort.

Claude Code is incredible, really.

Nothing that hasn't been said already, but two observations:

  1. It's surprisingly good at brainstorming solutions, if you're smart and willing to push back. It will suggest a bunch of frameworks -- I would strongly recommend not doing that (as of June 2025--who knows what will happen in just a few months!). These new trendy frameworks are not lindy enough, they don't have a sufficiently large corpus for AI to learn from, the code Claude will write is going to be all wrong.
  2. Claude Code is good for work, but incredible for personal projects. Its biggest strength is getting things done that you wouldn't have done otherwise. I have so many "todos" and "improvements" to make but I know it's going to take a few days or even weeks, so I push them back indefinitely. For example I have wanted to build a global blob store for AGES, but the pain of reading through the documentation, implementing it, migrating all the paths, etc., I could definitely have done it myself, but it would have needed a couple of weekends, and so I always pushed it back. Because Claude can write decent-to-good code so quick, it can get it done in hours instead of days. This reduces the activation energy and offers what is in effect an "infinity" productivity improvement, because the counterfactual is that these improvements just don't get built without it.
  3. It's incredible for writing documentation too! I got it to write the CHANGELOG after making some changes, check out a sample entry:

Added

  • Automatic Link Conversion: Created markdown-it-obsidian-links.js plugin to automatically convert Obsidian-style relative markdown links to Eleventy-compatible absolute paths during build
  • Plugin Integration: Integrated the link conversion plugin into Eleventy's markdown processing pipeline

Fixed

  • Obsidian Link Compatibility: Resolved incompatibility between Obsidian's relative link format ([text](filename.md)) and Eleventy's expected absolute path format ([text](/filename))
  • Content Consistency: Converted all manually converted absolute path links back to Obsidian-style relative paths for consistent authoring experience

Changed

  • Link Format Standardization: All content now consistently uses Obsidian's native relative link format (filename.md) while the plugin handles automatic conversion to absolute paths (/filename) during build
  • Date-based Link Conversion: Converted date-based permalink URLs (e.g., /2020/08/03/blog-rebuild-2020/) back to markdown file references (2020-08-03-blog-rebuild-2020.md) to leverage Eleventy's permalink system

Technical Details: The plugin operates during markdown processing, detecting relative .md links and converting them to absolute paths while preserving external URLs and non-markdown links. This solution eliminates the need for manual link format maintenance across 78+ affected files while ensuring seamless integration with Eleventy's routing system.

I think this is pretty awesome, it can make the changes AND document it. It is a bit verbose, but it's a lot better than what I would have written myself (mainly a patience thing)

further work

  • (bugfix) current rsync cronjob sucks and breaks often
  • (bugfix) tasker doesn't work well with photos taken with cameras
  • full-text search of the entire website
  • canvas display support
  • comment feature, ideally with threads

conclusion

I now have the following:

data store

  1. blob storage, running at blob-service.lieu.gg and serving files at files.lieu.gg and
  2. second brain (Obsidian vault + GitHub repo)
  3. secrets vault (TODO)

views

  • lieuzhenghong.com or lieu.gg (static site, public by default)
  • (in future) private view of my second brain, with full-text search over all my private notes
  • (maybe, in future) offer services like semi-custodial crypto wallet for friends and family

Appendix

setting up blob storage

R2 and cron

Set up a Cloudflare account, set up R2 Object Storage

Set up rclone on the desktop

Set up bisync:

rclone bisync ~/Documents/lieu-blobs/ r2:lieu-files/ --resync for first time, then

Set up cronjob to sync

crontab -e */2 * * * * /opt/homebrew/bin/rclone bisync ~/Documents/lieu-blobs/ r2:lieu-files/

log show --predicate 'process == "cron"' --info --last 1d