Cover image for Why You Should Try Nanoviews
•
6 min read

Why You Should Try Nanoviews

Table of Contents

For the last three years, I’ve been building Nanoviews, a small UI library on the same signals core as Nano Kit. A view is a tree of plain function calls that builds real DOM nodes once, and the signals bound into it update those nodes in place: no JSX, no virtual DOM, no re-renders.

If you read my post about Nano Kit 2.0, you’ve already seen Nanoviews at work: the DevTools panel from that post is a Nanoviews app.

Nanoviews just hit 1.0, and in this post I want to give you four reasons to try it. The quickest way to get a feel for it is the weather example on StackBlitz.

1. It’s just functions and real DOM

Each HTML element has a factory named after its tag: attributes go in the first call, children in the second. A component is a function wrapped in component$, and its render runs once. Here’s a whole todo list:

import { signal, length } from 'nanoviews/store'
import { form, input, ul, li, p, mount, component$, for_ } from 'nanoviews'
const Todos = component$(() => {
const $todos = signal(['Release 1.0'])
const $draft = signal('')
return (
form({
onSubmit(event) {
event.preventDefault()
$todos(todos => [...todos, $draft()])
$draft('')
}
})(
input({
placeholder: 'What next?',
value: $draft
}),
ul()(
for_($todos)(
$todo => li()(
$todo
)
)
),
p()(
'Total: ', length($todos)
)
)
)
})
mount(Todos, document.querySelector('#app')!)

Typing writes $draft, submitting writes $todos, and only the nodes bound to them change: for_ adds one li, and the counter’s text node updates. The render never runs again, so there’s nothing to memoize and no dependency arrays to keep in sync.

There’s no compiler and no JSX transform either: views are plain JavaScript that runs as written, so you don’t even need a build step. Here’s a complete page that imports Nanoviews straight from a CDN:

<div id="app"></div>
<script type="module">
import { signal } from 'https://esm.sh/nanoviews@1/store'
import { button, component$, mount } from 'https://esm.sh/nanoviews@1'
const Counter = component$(() => {
const $count = signal(0)
return (
button({
onClick() {
$count(count => count + 1)
}
})(
'count is ', $count
)
)
})
mount(Counter, document.querySelector('#app'))
</script>

With TypeScript, every element, attribute, event and child is typed, so a misspelled attribute is a type error.

2. It’s small

Nanoviews is around 4 kB gzipped for what a typical app imports, and a bit over 7 kB with every export, signals included. Every element, block and helper is a separate export, so you only ship what you use.

Here’s what that means for a real app: the same weather app built on different stacks.

StackRaw SizeGzipped Size
Nanoviews + Nano Kit27.98 kB10.36 kB
Solid + Nano Stores36.41 kB13.55 kB
Svelte + Nano Kit61.74 kB23.12 kB
React + Nano Kit211.39 kB66.49 kB
React + TanStack Query232.56 kB71.56 kB

The sum of the minified JS chunks that vite build emits for the browser. All rows were built with Vite 8 on the same day in October 2026.

The gap holds for bigger apps. The Rick and Morty encyclopedia, with routing and data fetching, is 17.75 kB gzipped with Nanoviews, 36.20 kB with Svelte and 73.70 kB with React, all three on the Nano Kit query and router, and 102.34 kB with React, TanStack Query and TanStack Router.

3. It’s fast

Building the DOM once and updating it in place keeps the work small. Here’s a js-framework-benchmark run from October 2026:

LibraryDurationMemory
Vue Vapor 3.6.0-rc.51.021.27
Solid 1.9.31.021.01
Svelte 5.42.11.071.14
Nanoviews 1.01.111.05
Vue 3.5.391.181.47
Preact 11 + Signals1.272.93
Angular 22 (signals, zoneless)1.271.80
React 19.21.362.03

Geometric means of the slowdowns, each result divided by the fastest one in its test, so lower is better. Full results are on the Nanoviews site.

Vue Vapor, Solid and Svelte are a little faster, but all three compile your templates or JSX. Nanoviews gets within 10% of them with plain function calls and no compiler of its own, it’s ahead of Vue, Preact, Angular and React, and it uses the least memory after page load of anything in the table.

4. It comes with Nano Kit

Nanoviews gets its reactivity from the Nano Kit core, so everything built on that core binds into a view as it is: a query result, a URL parameter, a translated message or a value from localStorage, with no adapter in between.

import { localStored } from '@nano_kit/platform-web'
import { button, component$ } from 'nanoviews'
const $theme = localStored('theme', 'light')
const ThemeSwitch = component$(() => (
button({
onClick() {
$theme(theme => theme === 'light' ? 'dark' : 'light')
}
})(
'Theme: ', $theme
)
))

That’s how the Nano Kit DevTools panel is built: its stores are plain @nano_kit/store, and its views are Nanoviews. The rest of the toolbox is there too:

Server-side rendering isn’t there yet: it’s the next feature on the list. Until then, Nanoviews renders in the browser, which is all a single-page app or a widget needs.

Try it

Spin it up in a sandbox or use it for a widget where every kilobyte counts, and see how it feels. Give it a try!