I Wanted A Blog
I'd already been learning Rust for a while, so I spent three days working through Watery Desert's Rust Axum Askama tutorial. It was an excellent playlist that taught me new things about:
Then I Just Used AI
I already knew AI was great for learning new things. I use the Claude desktop app pretty often for that. But Claude Code is something completely different - it really does things. I decided to use it to build the blog, taking it slow, one feature at a time, and it just did everything for me...
Ultimately "we" created a self-contained blog engine built in Rust that parses Org-mode files into HTML using orgize with frontmatter support. It renders them through Askama templates served by an Axum web framework. Content, static assets, and the web server itself is packaged into a single binary using rust-embed.
The styling uses a custom CSS design system ported from Protesilaos Stavrou's ef-maris Emacs theme with rainbow-colored headings. Code blocks get syntect-powered syntax highlighting. A theme toggle switches between dark and light modes with localStorage persistence and system preference detection.
The site has search with server-side matching and context excerpts.
Custom typography uses Farro for headings and JetBrains Mono for code.
The server runs through tower-http middleware with gzip compression.
Rate limiting protects the site using the tower-governor crate,
which applies limits based on the X-Forwarded-For header to properly
handle requests coming through Cloudflare and the Caddy reverse proxy.
Posts automatically display last updated timestamps extracted from git commit history - a build script pulls the last commit date for each post and embeds it at compile time, only showing the updated date when it differs from the publication date.
The standalone binary is deployed in an LXC container on my Proxmox home server, behind a Caddy reverse proxy with automatic HTTPS, and fronted by Cloudflare.
I also asked Claude Code to create an AGENTS.md file with formatting
rules for writing posts. This adds hyperlinks where necessary and
formats code fragments.
Claude Code also handled all the git stuff for me - commits, branches, the whole thing. I didn't have to think about version control at all, which is a huge relief.
I had a small home server I bought earlier this year but forgot how I'd set it up. Claude Code helped me figure out what was going on and get everything working again.
The complete source code is available on GitHub.
Hiccups
There were a few hiccups. Once it tried to install Docker in an LXC container, which wasn't necessary. Just asking what it was doing set it back on track though.
Security needed attention. When setting up port forwarding on my router, Claude Code didn't make sure I only opened those ports to Cloudflare, my web reverse-proxy service. I had to catch that myself.
Claude Code doesn't read the AGENTS.md file on startup - only the
.claude/CLAUDE.md file. I wanted formatting rules and guidelines in
a separate file that Claude Code would follow for every session. The
solution was adding instructions to .claude/CLAUDE.md telling it to
read the AGENTS.md file at the start of each conversation.
Version upgrades were surprisingly difficult. When I asked Claude Code to upgrade to a major version of Askama, it got really confused about some pretty simple things. It couldn't figure out basic Askama usage patterns until I explicitly explained how the templating worked. This was strange because it had been working with Askama successfully earlier.
When adding rate limiting, Claude Code hallucinated and used a completely wrong method. This was the only straight up hallucination I saw during the project.
Claude Code's world knowledge is outdated. It tried to use once_cell instead of the built-in std::sync::LazyLock that's now available in standard Rust. I had to prompt it to fix this.
Future Ideas For Working With Claude Code
Next time I work with Claude Code, I'm going to ask it to generate more shell scripts instead of running commands directly. Sometimes it's difficult to follow everything it's doing. Having scripts I can review and execute myself would give me better visibility and control over the process.
I also need to find a tool to review the diffs side-by-side. It would be easier to see what's changing.
Adding the exa-mcp server was helpful for getting info on libraries and APIs, so I'll continue doing that.
Starting with the latest versions of libraries would avoid upgrade problems down the line. The version upgrade difficulties showed that Claude Code handles greenfield development better than migrating existing code to new major versions.
Using The Blog Engine
The blog engine is simple to use once it's set up. To run the local development server, just use:
This starts the server locally so you can preview changes before deploying.
To setup a remote Ubuntu server, run:
This configures the server infrastructure including Caddy, systemd services, and required directories. After setup is complete, deploy to the server with:
The Just command runner handles all the compilation and deployment steps.
Adding new posts is straightforward - create a new org mode file in
the content/posts/ directory with frontmatter like #+TITLE: and
#+DATE:. The blog engine automatically picks up new files and
renders them. Editing existing posts works the same way - just update
the .org file and redeploy.
Everything It Did, Or Walked Me Through
Here's a list of everything Claude Code either implemented directly or guided me through setting up. It's honestly impressive how much ground "we" covered:
Initial Blog Engine Creation
Built entire blog from scratch using Rust, Axum, and Askama templating
Org-mode support - Parse and render
.orgfiles with frontmatter (TITLE,DATE) using orgizeEmbedded architecture - Single binary with all content and static files baked in using rust-embed
Syntax highlighting - Integrated syntect for code blocks in org-mode posts
Basic routes - Index page listing posts, individual post pages, static file serving
Theme & Design System
Ported ef-maris themes from Emacs - Brought over both dark and light variants from Protesilaos Stavrou's ef-themes
Theme toggle - Built functional dark/light mode switcher in header with SVG sun/moon icons
Smart theme detection - Respects system preference
(prefers-color-scheme)on first visitLocalStoragepersistence - Remembers user's theme choiceSystem theme listener - Auto-switches with OS theme if user hasn't manually set preference
Smooth transitions - CSS transitions between theme switches
Typography & Color
Custom font stack:
Headings: Farro (Google Fonts)
Code: JetBrains Mono
Body: System fonts
Rainbow org-mode headings - Implemented colored
H1-H6using ef-maris palette:H1:green-cooler,H2:blue-warmer,H3:green-warmerH4:cyan,H5:magenta-cooler,H6:blue-cooler
Full CSS variables - Comprehensive design system with
--fg-primary,--bg-secondary,--color-link, etc.
Search Functionality
Server-side search with
/search?q=queryendpointHeader search form - Integrated into header next to theme toggle
Search results page with context excerpts showing
~200 charsaround matchesHTML entity decoding - Cleaned up entities in search results (quotes, ampersands, etc.)
Smart excerpt generation - Shows context around search terms with word boundary detection
UI/UX Polish
Aligned header controls - Theme toggle and search button same height
Dynamic copyright year - JavaScript-based auto-updating footer
Code copy buttons - Added to all code blocks with visual feedback
Responsive design - Mobile-friendly search forms and layouts
Accessibility - ARIA labels, semantic HTML, keyboard navigation
Deployment Infrastructure
Caddy reverse proxy - Automatic HTTPS with Let's Encrypt for
wall.ninjasystemd service - Auto-restart on failure, proper logging
Cloudflare + OPNsense - DNS proxy, DDoS protection, port forwarding
Created
setup-blog.sh- Automated server provisioning scriptCreated
justfile-just deploycommand for one-command deploymentsCross-compilation setup - cargo-zigbuild + zig for Mac → Linux builds
Performance & Reliability
HTTP compression - Gzip compression via tower-http
Rate limiting - Implemented with tower-governor using
X-Forwarded-Forheader for proper client identification behind reverse proxyZero-downtime deploys - systemd automatically restarts service after binary update
Documentation
Comprehensive
README- Tech stack, project structure, deployment instructions, design creditsCross-compilation docs - Why zig is used and how to set it up
Server setup guide - Complete infrastructure documentation
Example Output
Here's an example of what the rendered content looks like:
Headline 2
Headline 3
Headline 4
Headline 5
This is some text with a link. This is some code text.
| Here | Is | A | Table |
|---|---|---|---|
| Welcome | to | my | blog |
| Please | enjoy | your | stay |