Interactive web apps in pure Nix
Elements, styles, state, and side effects, all written in Nix. Available on npm as pre-built WebAssembly.
Try it
A minimal cursery.nix app. Edit it and see.
Enable JavaScript to try the demo.
$ nix flake init -t github:PsychoLlama/cursery.nixFeatures
Virtual DOM
renderreturns a tree of elements. After each update, the new tree is diffed against the old one, and only what changed touches the DOM.Scoped styles
cssturns a block of CSS into a class, named by its hash. Nest selectors and media queries inside it.Hot reloading
Save a
.nixfile and Vite swaps in the new code. The app keeps its state.Declarative effects
Effects are values, chained with
io.pipeandio.catch. Returned by event handlers orupdateand executed by the runtime.Host interop
Pass JavaScript functions to
mountand call them from Nix withio.call. If one returns a promise, Nix waits for it.Static rendering
renderToStringrenders an app to HTML. The Vite plugin uses it to prerender pages, and the app mounts over them in the browser.
Examples
Hello world →
Type your name, see text update.
TodoMVC →
The classic todo list, with filters and editing.
This page itself! The entire site is written in Nix, pre-rendered and hydrated. See the source.
History
Cursery started as a joke. It's still a joke. This is not a serious project.
NixCon 2026 had two extra days for hacking, and while I didn't have any good project ideas, I did have some evil ones.
Cursery's first prototype surprised me. Eval times are notoriously slow, and yet counters could increment at a cool 60fps. A wonder of science. How much more dirt could I dig from this grave?
I spent another week evolving it into a plausible web framework. There's more to build, but I miss my free time, and this feels like a good place to stop.
Thanks, Snix
Snix is a Rust implementation of Nix. Its evaluator compiles to WebAssembly, which is the whole trick. Cursery wouldn't be possible without their incredible work.