Skip to content

Migrating to Kerf

Kerf doesn’t try to replace your framework. But if you’ve already decided to leave one — or you’re building something new and weighing the cluster — these pages show your current code translated, line for line, into kerf.

The reference app is a classic todo list: add, toggle, delete, filter. Roughly 150 lines in every framework. Big enough to exercise state, keyed lists, delegated events, and persistence. Small enough that you can read the whole thing. (A few pages — htmx, Redux, Astro — use a conceptual mapping instead of a literal TodoMVC because the source isn’t directly comparable.)

Not ready for a full rewrite? You don’t need one. kerf coexists with React/Vue/Svelte on the same page — see Adopting kerf incrementally to migrate one island at a time.

How kerf compares to each source framework on the dimensions developers usually decide on.

DimensionReact 19Vue 3Svelte 5SolidPreactAlpine 3Lit 3vanjs 1.5kerf
Bundle (min+gz, runtime only)~45 KB~22 KBvaries (compiled)~4.5 KB~6 KB (w/ signals)~14 KB~6 KB~1.6 KB~11 KB
Reactivity modelhooks + virtual DOMrefs + proxiesrunes (compiled)signals + compilerhooks + virtual DOMper-component x-data proxiesreactive properties + lit-htmlsignals + direct DOMsignals + DOM morph
Component modelfunction / class componentscomposition + SFCrunes inside .sveltefunction componentsfunction componentsHTML directivesweb componentshyperscript factoriesplain functions returning JSX
TemplatingJSX → virtual DOM<template> DSL.svelte DSLJSX (compiled)JSX → virtual DOMHTML attributestagged-template literalshyperscript / DOM nodesJSX → HTML strings → morph
Compiler requiredno (but @vitejs/plugin-react)yes (@vue/compiler-sfc)yesyes (babel-plugin-jsx-dom-expressions)no (but plugin)nononono
Keyed list reconciliationkey prop, virtual-DOM diffv-for :key{#each ... (id)}<For each>key propmanual via x-for :keyrepeat() directivemanual via signal arrayseach(items, render, key)
Focus / selection survival across re-rendermanual via refsmanualusually automaticusually automaticmanual via refsmanualmanualmanualautomatic in morph()
Event handlingper-node JSX handlers@click= attributesonclick={fn} on JSXper-node JSX handlersper-node JSX handlers@click= attributes@click= template directiveshyperscript handlersdelegate() once on the root
Server renderingfirst-classfirst-classfirst-classfirst-classfirst-classnonedeclarative shadow DOMvanX.replace storySafeHtml.toString()
State sharing across componentsContext / Redux / Zustandprovide/inject + PiniaSvelte storescreateStoreContext / signalsAlpine.storeglobal state libsshared van.state()defineStore

(Alpine, htmx, Angular, jQuery, Redux, and Astro aren’t directly comparable on every row — see those specific pages for the conceptual mapping.)

Cross-framework perf comparisons are only published from official benchmark runs — clean machine, no background load, results re-generated under controlled conditions. The current committed numbers live at bench/results.md. Headline cluster positions (lower is better on the krausest js-framework-benchmark):

  • create 1k / replace 1k / append 1k — kerf, Vue, Lit, vanjs, Preact, React are all within ~20% of each other; Solid leads.
  • partial update — Solid and Vue lead; Lit, Preact, React are tightly clustered; kerf and vanjs sit in the next band.
  • select row — Solid, Vue, React lead a tight cluster; kerf sits at the top of the band with Preact and Lit.
  • swap rows — kerf, Solid, vanjs, Vue, Lit lead a tight cluster; React is an outlier (much slower).
  • remove row / clear 1k — within typical noise across the cluster.

The deciding factor between most of these frameworks is the architecture / bundle / tooling tradeoff covered in each per-framework page, not row-update latency. Solid and Svelte 5 are the exceptions where compiler-driven update-path performance is a real, measurable lead — if that’s your decision driver, those are the right answers.

How the per-framework pages are structured

Section titled “How the per-framework pages are structured”

Each page (with adjustments for non-TodoMVC sources) follows the same five-section shape:

  1. Bundle delta — what the swap costs (or saves) in bytes.
  2. Mental-model translations — a table mapping the source framework’s primitives to kerf’s.
  3. Side-by-side code — the same todo list, side by side, section by section (state, render, events, list).
  4. Gotchas — what trips developers coming from this specific framework.
  5. Perf numbers — qualitative or quoting bench/results.md where the framework is in the comparison set.

If your framework isn’t listed, the React page is the closest fit for any hooks-shaped framework, the vanjs page is closest for “no template language, direct DOM,” and the jQuery page is closest if you’re modernizing from a non-reactive imperative codebase.

If your app is built around heavy component composition (<DataGrid>, <DatePicker>, deep prop drilling), kerf probably isn’t your next stop — see When to reach for something else.