improving the site (2025)
Sun Jul 06 2025tags: 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:
- 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.
- 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.
- It's incredible for writing documentation too! I got it to write the CHANGELOG after making some changes, check out a sample entry:
[2025-06-21] - Obsidian Link Compatibility
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 systemTechnical 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
- blob storage, running at blob-service.lieu.gg and serving files at files.lieu.gg and
- second brain (Obsidian vault + GitHub repo)
- 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