← Back to BlogDEAuf Deutsch lesen

The State of solid-ml

The solid-ml experiment is coming to an end. Not with a dramatic failure, but with the quiet realization that it's too much friction.

The framework kind of works. Tests pass. SSR renders. Hydration hydrates. But every time I sit down to actually build something with it, LLM agents run into DX papercuts that shouldn't be there. And fixing those papercuts without LLMs turning the solid-ml codebase into a complete mess felt like it was taking way too long and would get way too complex. I just didn't have the time to properly get involved myself in the codebase, and now I'm bearing the consequences.

What Went Wrong

I think I took a few wrong turns to get here.

The biggest one: setting the target to "full SSR with hydration." That introduced a ton of complexity - hydration markers, isomorphic module interfaces, the Html_intf.S shared interface between server and browser runtimes, template compilation that has to produce matching output on both sides. Each of these is a hard problem on its own. Stacking them together in an LLM-driven codebase made the whole thing fragile in ways that are difficult to debug.

SSR with hydration is the kind of feature where correctness matters. Off-by-one in marker indices. Whitespace differences between server and client rendering. Event delegation timing during hydration. These are the bugs that don't show up in unit tests but break real applications. And when the code was written by AI agents, tracing those bugs back to their root cause is hard because I didn't always follow along in detail to understand what was going on.

What I Actually Wanted

In any case, I realized that what I wanted was different from what I had the agents build.

I wanted something like Elm but with OCaml types and enough syntax compatibility to share validation logic between frontend and backend. Compiled to JavaScript like SolidJS - small bundles, fine-grained reactivity, no virtual DOM overhead.

Basically a client-side reactive framework wrapped in a type-safe OCaml layer that produces efficient JavaScript. With SSR, though. Hydration is IMO best avoided in favor of serving static non-interactive pages with placeholders that upgrade to become interactive when the JavaScript loads.

If I'd started there, the project would have been an entirely different thing.

What Worked

MLX as a template language for OCaml is excellent. Writing components in .mlx files feels natural if you've used JSX before, and the compiler catches mismatched tags. This is the part of the stack I'd want to keep.

The AI-driven development process itself was the real experiment, and it taught me a lot about where LLMs can and can't carry a project (yet?). They're great at the initial momentum - getting a working prototype with tests in days instead of weeks. They fail at the kind of architectural consistency you need once a codebase reaches a certain size. The reactive core was rewritten twice in the first two days because the agents didn't get the SolidJS architecture right on the first pass.

What's Next

I'm not abandoning the idea of OCaml on the frontend. I'm just accepting that solid-ml as a full-stack framework with SSR isn't the right vehicle for it. At least not the way solid-ml is right now.

It also feels like I don't really need solid-ml. When I have LLM agents write SolidJS directly - plain TypeScript, no OCaml - the experience is fine. The agents produce decent SolidJS, the ecosystem is mature, and I get SSR for free.

For projects where I want OCaml's type safety on the frontend, there are lighter-weight approaches that don't require porting an entire framework. Also, there's still a good bunch of OCaml frameworks and tools I haven't tried yet and that I should try in order to make up my mind. Eliom offers full-stack OCaml with SSR baked in. Bonsai and Incr_dom are Jane Street's approaches to web UIs. And there's always the option of using Melange with ReasonReact bindings directly.

The solid-ml repository stays public. The code is there, the tests (how meaningful or meaningless they may be) pass.

But I'm not going to keep investing more time in solid-ml to make it work. Because I also need to make money at some point.

However, the places where LLM-driven OCaml development is actually paying off are API bindings. ocaml-social-sdk is a library for accessing social media APIs that I'm actually using. So it's getting better. Same with ocaml-stripe and ocaml-oauth2-clients. API bindings are slop either way - and sharing them publicly to build on shared foundations means we'll have battle-tested API clients available for everyone. And best of all, LLMs are capable of building API clients in continuation passing style, so that it's easy to support all concurrency runtimes (lwt, eio, Async, miou, etc).


solid-ml is a SolidJS-inspired framework for OCaml. Built with Melange and MLX.