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.

Dimension React 19 Vue 3 Svelte 5 Solid Preact Alpine 3 Lit 3 vanjs 1.5 kerf
Bundle (min+gz, runtime only) ~45 KB ~22 KB varies (compiled) ~4.5 KB ~6 KB (w/ signals) ~14 KB ~6 KB ~1.6 KB ~11 KB
Reactivity model hooks + virtual DOM refs + proxies runes (compiled) signals + compiler hooks + virtual DOM per-component x-data proxies reactive properties + lit-html signals + direct DOM signals + DOM morph
Component model function / class components composition + SFC runes inside .svelte function components function components HTML directives web components hyperscript factories plain functions returning JSX
Templating JSX → virtual DOM <template> DSL .svelte DSL JSX (compiled) JSX → virtual DOM HTML attributes tagged-template literals hyperscript / DOM nodes JSX → HTML strings → morph
Compiler required no (but @vitejs/plugin-react) yes (@vue/compiler-sfc) yes yes (babel-plugin-jsx-dom-expressions) no (but plugin) no no no no
Keyed list reconciliation key prop, virtual-DOM diff v-for :key {#each ... (id)} <For each> key prop manual via x-for :key repeat() directive manual via signal arrays each(items, render, key)
Focus / selection survival across re-render manual via refs manual usually automatic usually automatic manual via refs manual manual manual automatic in morph()
Event handling per-node JSX handlers @click= attributes onclick={fn} on JSX per-node JSX handlers per-node JSX handlers @click= attributes @click= template directives hyperscript handlers delegate() once on the root
Server rendering first-class first-class first-class first-class first-class none declarative shadow DOM vanX.replace story SafeHtml.toString()
State sharing across components Context / Redux / Zustand provide/inject + Pinia Svelte stores createStore Context / signals Alpine.store global state libs shared 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. Section by section — the same todo list translated 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.