Introducing willow.camp
Cassia Scheffer
Published on June 28, 2025
I’ve been working on a side project and am finally ready to share it! My blog, enumerator.dev, is now hosted on my platform called willow.camp.
willow.camp started because I wanted a platform that felt like me. I wanted a simple editing interface, the ability to import and export every post in markdown, and a pared-down reading interface.
My blog was previously hosted on Ghost, but the experience felt like an “influencer” blog to me. I’m a regular developer sharing the projects I work on, and I don’t need a big newsletter distribution list or metrics on open rates.
I looked at bearblog.dev and pika.page as alternatives to Ghost. They are both lovely platforms, and willow.camp is inspired by them, but they didn’t quite meet my needs. So I took the plunge and typed rails new willow_camp
in my terminal.
Well, okay, I admit. First, I tried Elixir; then I read about doing it in Rust and tested out Hanami before realizing that I wanted to create something more than I wanted to learn a new language.
Sometimes speed is about using a batteries-included framework more than it is about milliseconds of server render time.
The willow.camp Stack
willow.camp is built on Rails 8, Turbo, Hotwire, Stimulus, and DaisyUI. These libraries have made it easy to spin up scaffolding and prototype ideas. A considerable part of the initial commits in willow.camp are all rails scaffold
(not even AI!). As things took shape, I polished off features using context7 to pull docs into Zed hooked up to GitHub Copilot.
So far, here are a few libraries I’ve used to develop willow.camp:
- acts-as-taggable-on for tagging posts.
- marksmith for a markdown editing UI.
- commonmarker for parsing and rendering markdown.
- front_matter_parser for parsing YAML front matter on markdown files.
- friendly_id for post slugs.
- pagy for pagination.
- public_suffix for validating custom domains without a complex regex.
Hosting
willow.camp is hosted on a DigitalOcean instance with a managed Postgres database. Deployments and configs are managed through hatchbox.io.
APM, Errors, Emails
I hooked up Scout Monitoring for APM and Honeybadger for error tracking. For now, I’m well below the usage limit for both, so I’m on the free plan. I’m happy with both of those, but I find the log management in Scout to be somewhat clunky on my tiny MacBook Air’s screen. I’m not sure I’d pay for the logging aspects of Scout, but everything else meets my needs.
Password reset emails are sent through loops.so. Loops was straightforward to set up, and transactional emails are free.
Dashboard
As mentioned above, the editing interface is marksmith, an easy-to-use, plug-and-play markdown editor with a built-in preview feature. Posts are saved in both markdown and HTML formats. This makes exporting content through the API straightforward because Markdown is the base format in which posts are written and saved. HTML is only used for display.
Mermaid Diagrams
You can add a code block with mermaid
as the language, which will render a mermaid diagram on the page in place of the code block. Have a look at How Rack::Timeout Keeps Your App Alive for an example.
Auto-saving Posts
I like to start a draft post, then walk away and think about it. While I enjoy writing in Obsidian, I often use my blog “drafts” as a place to store ideas.
I added a stimulus controller that auto-saves draft posts. It checks if the post is “dirty” (i.e., changed since the last save) and saves it every 60 seconds. You can also hit Command/Control + S to save while you type.
Auto-save is disabled on “published” posts because I wouldn’t want a half-edited post to be saved and published.
No Image Hosting
I don’t host images or static content aside from what’s in the Rails asset pipeline. This is intentional right now. I want to keep hosting complexity to a minimum, and I didn’t want to worry about storage services, mounting volumes, or data retention. For now, I’d like the server to be ephemeral.
Favicons, Themes, Subdomains, & Custom Domains
Of course, I spent the most time on the settings page.
You can preview custom favicons and themes on the willow.camp home page by clicking the menu items in the top right. The themes come from DaisyUI. The favicon functionality still requires some refinement.
How willow.camp (sort of) Generates Favicons
Users can insert any emoji into the “favicon” settings form. The emoji is saved to their settings, and then when the page loads, it is rendered in in-memory canvases to generate the various sizes of favicons needed and insert them into the head
as an image in a URI scheme like so:
<link type="image/png" rel="icon" sizes="16x16" href="data:image/png;base64,...lots of characters=">
This works for most cases, but since the image doesn’t exist until the JS renders it, some browsers may not display a favicon at all, and there are no Open Graph images available for link previews on social sites.
Better Favicons in the Future
I want to fix the favicon functionality. I think the best way to do this reliably is to pre-generate images for 10-20 emojis that users can pick from. This way, I only have to store a set number of assets on the server and can provide real links to favicon images.
Another possibility is to render a canvas in the browser when the user selects the desired emoji. Then, when they update their settings, save the favicon as an attachment and generate the correct sizes on the server. This would provide flexibility to use any emoji, but it would also require hosting more static content, which I didn’t want to do yet.
Themes
The DaisyUI themes are fun! I borrowed their theme picker HTML and used a Stimulus controller to give instant previews of each theme. Users’ theme preferences are saved in the DB and rendered in the HTML.
I experimented with allowing users to choose between a light and dark theme, but couldn’t get it to work to my satisfaction. prefers-colour-scheme works great with DiasyUI’s default light
and dark
, but I didn’t see an easy way to pass a user’s preferences from the database into the CSS without compiling the assets on each request.
I did have a light/dark functionality sort of working with a Stimulus controller and some data attributes in the HTML, but there was a brief flash of the default light
or dark
scheme before the chosen scheme was loaded.
For now, you can pick one colour scheme for your willow.camp blog.
Subdomains and Custom Domains
Thanks to hatchbox’s Caddy setup, subdomains and custom domains are served over HTTPS. Each blog has a subdomain like cassia.willow.camp and an optional custom domain like enumerator.dev. Users point a CNAME record to their subdomain to set up a custom domain name.
SEO
It has been nearly a decade since I had to think about SEO. I’ve added Open Graph tags to the pages, and every site gets a sitemap.xml
and robots.txt
. I tried to use semantic HTML as best I could and asked Claude to audit the pages.
No Analytics
I had plausible.io on my blog for a very brief period, but the analytics provided too much information for me. I just want to share what I write. I don’t need graphs.
If you want to use willow.camp for your blog and want analytics, let me know, and I can add a feature to let you insert the code you need.
Accessibility
I need to do another accessibility audit on willow.camp. I tried to ensure the HTML used aria labels that were clear and concise.
I recently found CapybaraAccessibilityAudit, which I’ll use to audit the dashboard and public-facing pages.
About Page
The “about” page is a bit of a hack. “Pages” and “Posts” are almost the same thing in the database. I used single table inheritance to create a single “about” page for each user. Every user always has one about page. If you delete your “about” page, a new draft page is created in the background.
willow.camp doesn’t use pages otherwise. Everyone gets one “about” page.
Feeds: RSS, Atom, JSON
RSS and the indie web are having a comeback. I’ve been reading RSS on Feeeed and Feedbin lately, so proper feeds are essential to willow.camp.
My pet peeve with “follow me on RSS” links is that most of the time, you end up downloading a file when you click on the link. That’s not helpful.
I drew inspiration from Subscribe Openly and created a helpful “Subscribe” page with clipboard helpers to copy the feed of your choice easily. Check it out: enumerator.dev/subscribe.
API and CLI
willow.camp comes with an API and CLI tool for managing content. I’ve used the CLI to import and format my content from Ghost.
The CLI converts most of the HTML from Ghost correctly, but leaves some extraneous markdown that requires manual cleanup.
Todo list
I have lots of things I still want to do on willow.camp. Here are a few things I have in mind:
- Write an Obsidian extension. I got Claude, Copilot, and Zed to build the CLI in Ruby. It does the job. However, what I’d like is a full-featured Obsidian extension that allows me to write, edit, publish, and sync my blog directly within Obsidian.
- Re-implement syntax highlighting in code blocks. I think I messed up code syntax highlighting somewhere along the way. I want to fix this.
- Fix the CLI tool’s gem publishing. Something borked here and I haven’t had a chance to fix it. If you want to use the latest CLI, you can build it from the repo for now.
- Add a manual “light” and “dark” toggle to toggle between a user’s preferred schemes. This wouldn’t use
prefers-color-scheme
but would still allow light and dark mode. - Disallow a light scheme to be paired with Dracula because Dracula lives in the dark.
New Features
- I love RSS, and I mostly love most RSS readers. But like a blogging platform, I’ve had trouble finding one that fits me. I want to build an RSS reader functionality into willow.camp so you can read RSS in the same place you publish.
- Bookmarks! I love everything about raindrop.io, but I also like the challenge of building my own. So why not make willow.camp the super app of the indie web and add bookmarks? I’m joking here. I’d create a bookmarking feature for myself and hope that others will like it too.
- Maybe introduce distribution lists? I liked that people could get my posts by email on Ghost. I didn’t want to see the open rates and graphs. I might re-introduce email distribution with a more straightforward UI.
Thoughts on Stimulus, Hotwire, and Turbo
Wow. I love this. Honestly, it makes me love developing web apps again. There is no heavy complexity between the frontend and backend. You don’t need to shift mindsets to start writing in a frontend framework.
I didn’t expect to enjoy these features this much. However, I’m back to writing HTML that maps directly to what I see in the browser, and a few extra bits of syntax in data-attribute
fields make it a snappy and dynamic UI.
willow.camp is Free
willow.camp is free to use. If people find it useful, I might introduce a paid tier to cover hosting costs.
If you want a willow.camp blog, you can sign up and try it out.