Weeknotes: Webbplats update (part 1)
May 28, 2026
This website is hosted using Webbplats, my own OCaml based thing that is kinda like a static site generator, but dynamic. It was built upon Dream, a web framework for OCaml, which is very good (and particularly well documented), but for various reasons, I want to port it over to something using the new OCaml Effect based I/O system, and that means moving away from Dream.
The challenge there is I wasn't just using Dream for the receiving of and responding to of HTTP requests, but Dream has a templating language for rendering web pages, and I was using both parts. So to make the transition a little easier, I thought I'd tackle that second part first, and if everything has worked, you're now reading pages rendered in other ways: using Htmlit for the web page rendering, and Syndic for rendering the RSS feed; a tip of the old hat to Anil Madhavapeddy for both recommendations. I rushed through the job somewhat, so I've probably missed a few bits here and there, but for the most part it seems to be working okay, particularly all the embed bits.
Htmlit means I'm now expressing my HTML in OCaml rather than as an HTML document with embedded bits of OCaml as I would with Dream. So rather than:
let render_video filename thumb_opt looped =
<div class="video">
<video controls
% (match looped with true ->
loop
% | false -> ());
% (match thumb_opt with Some t ->
poster="<%s t %>"
% | None -> ());
>
<source src="<%s! filename %>" type="video/mp4"/>
Your browser does not support the video element.
</video>
</div>
I now do something like:
let render_video filename thumb_opt looped =
let looped_at = if looped then [At.true' "looped"] else []
and thumb_at = match thumb_opt with
| Some t -> [At.v "poster" t];
| None -> []
in
let video_attrs = looped_at @ thumb_at in
El.div [
El.video ~at:video_attrs [
El.source ~at:[
At.src filename;
At.type' "video/mp4";
] ();
El.txt "Your browser does not support the video element."
]
]
The majority of effort in the migration was moving a thousand lines of template from one form to the other. Not fun, but I'm glad I laboured through it, as over the course of the process I got better at spotting where I could make nicer code and pull out common patterns into functions in a way the inline HTML templating didn't really support.
Neither are particularly readable forms, but I think I like to write in the former rather than the later as it makes the final HTML more obvious to me. But I did find it easier to write reusable fragments of HTML using the OCaml based Htmlit. I feel Htmlit has a lot of verbosity about it the way they've pushed all attributes into the ~at parameter, rather than having common/mandatory attributes be parameters to elements, but I suspect it stopped the author going mad trying to decide what was in and what was not. And despite preferring the former style, by the end Htmlit felt reasonably natural, and for complex pages is actually a bit cleaner as it's easier to split the logic up, whereas embedding it within the HTML dictates everything must be processed in the order of the HTML page.
For the feed I had to switch from RSS2 to Atom, because there was no good library in OCaml for generating RSS2 but there was in Atom. I was mostly just using RSS2 for legacy reasons, where I was using a template to generate the feed, which included having the date in the wrong format, as discovered by Anil. Now by using Syndic I'm hoping that'll all be good and proper - I guess we'll see when this does or doesn't appear in people's feed readers :)
Still, it was a couple of days hacking to move all my templating over, and now that is all done, I can at some point migrate the networking side of things over to remove Dream completely. Again, I really quite like Dream, so don't see this as a negative point on that framework, more that the reason I host on my own stack is to give me a safe place to play and experiment, and now I want to experiment with EIO.
Tags: weeknotes, self hosting, webbplats, dream