SolidJS Comparison
How Xote compares to SolidJS, especially where they share the same reactive model.
At a Glance
Overview
| Aspect | SolidJS | Xote |
|---|---|---|
| Reactive model | Fine-grained signals | Fine-grained signals |
| Component execution | Usually once | Usually once |
| Routing | Separate package | Built in |
| SSR | Strong framework story via SolidStart | Built-in primitives |
| Language | JavaScript / TypeScript | ReScript |
| Scope | Framework plus ecosystem | Smaller integrated UI library |
SolidJS and Xote are conceptually much closer to each other than either is to React. The real differences are language choice, what is included out of the box, and how explicit the UI bindings are.
Shared Ground
Shared Philosophy
Both libraries:
- use signals as the foundation
- avoid virtual DOM diffing for ordinary updates
- let components establish reactive structure once
- rely on dependency tracking instead of dependency arrays
If you already understand SolidJS, Xote's mental model will feel familiar quickly.
Runtime Model
Signals and State
SolidJS uses getter and setter pairs. Xote uses explicit read and write functions against a signal value.
1import { createSignal, createMemo, createEffect } from "solid-js";23const [count, setCount] = createSignal(0);4const doubled = createMemo(() => count() * 2);56createEffect(() => {7 console.log(count());8});1open Xote23let count = Signal.make(0)4let doubled = Computed.make(() => Signal.get(count) * 2)56Effect.run(() => {7 Console.log(Signal.get(count))8 None9})Key differences:
- SolidJS reads via function calls like
count(), Xote reads viaSignal.get(count) - Xote effects return cleanup directly; SolidJS uses
onCleanup - Xote signals use strict equality by default and let you opt into custom equality with
~equals
Component Model
Both component models are close: the function runs, the DOM structure is created, and later updates flow through reactive bindings instead of through repeated component renders.
The main ergonomic difference is that SolidJS can embed reactive expressions directly inside JSX, while Xote makes the reactive boundary explicit with helpers such as Node.signalText.
1function Counter() {2 const [count, setCount] = createSignal(0);34 return (5 <div>6 <h1>Count: {count()}</h1>7 <button onClick={() => setCount(c => c + 1)}>Increment</button>8 </div>9 );10}1let counter = () => {2 let count = Signal.make(0)34 <div>5 <h1>6 {Node.signalText(() => "Count: " ++ Int.toString(Signal.get(count)))}7 </h1>8 <button onClick={_ => Signal.update(count, n => n + 1)}>9 {Node.text("Increment")}10 </button>11 </div>12}List Rendering
SolidJS uses control-flow helpers like <For> and <Index>. Xote exposes list handling through Node.list and Node.keyedList.
1import { For } from "solid-js";23function TodoList(props) {4 return (5 <ul>6 <For each={props.todos}>7 {todo => <li>{todo.text}</li>}8 </For>9 </ul>10 );11}1let todoList = () => {2 let todos = Signal.make([{id: "1", text: "Buy milk"}])34 <ul>5 {Node.keyedList(6 todos,7 todo => todo.id,8 todo => <li> {Node.text(todo.text)} </li>,9 )}10 </ul>11}Both approaches preserve identity. Xote asks for the key function explicitly, which keeps the reconciliation contract visible in the code.
Platform Surface
Server-Side Rendering
SolidJS has the more complete application story through SolidStart. If you want file-based routing, streaming, and framework-level conventions, SolidStart is a major advantage.
Xote's SSR primitives are smaller and more manual. You render HTML, optionally serialize state with SSRState, and hydrate the result on the client.
1let html = SSR.renderDocument(2 ~scripts=["/client.js"],3 ~stateScript=SSRState.generateScript(),4 app,5)67Hydration.hydrateById(app, "root")Routing
SolidJS uses @solidjs/router rather than shipping a router in the core package. Xote includes one. That difference matters more for smaller apps than for large framework-driven apps, but it is still an important scope distinction.
Runtime Footprint and Compilation
Both libraries produce small output. The practical difference is in the toolchain:
- SolidJS uses a JSX compiler that turns reactive expressions into direct DOM operations
- Xote relies on the ReScript compiler and generic JSX transform, then uses explicit reactive nodes where needed
SolidJS feels more implicit in JSX. Xote is more explicit about where reactivity lives.
Type Safety
SolidJS with TypeScript is productive and familiar, but still lives in TypeScript's structural and partially unsound model.
Xote benefits from ReScript's stricter type guarantees. If that matters more to your team than staying in JS/TS syntax, it is a strong reason to prefer Xote.
Ecosystem
SolidJS has a meaningful and growing ecosystem, especially around SolidStart and headless UI work.
Xote is intentionally smaller. That means fewer ready-made packages, but also fewer architectural decisions outsourced to third-party dependencies.
Choosing Between Them
When to Choose SolidJS
- Reach for SolidJS when you want fine-grained reactivity while staying in JavaScript or TypeScript.
- Reach for SolidJS when the stronger ecosystem and framework story matter immediately.
- Reach for SolidJS when SolidStart and its conventions are part of the expected stack.
When to Choose Xote
- Reach for Xote when a smaller integrated API is more valuable than broader ecosystem depth.
- Reach for Xote when ReScript's type system and compilation model are part of the appeal.
- Reach for Xote when you prefer explicit reactive bindings over more automatic JSX transforms.
Migration Considerations
SolidJS developers typically need to adapt in three places:
- signal reads move from function calls to
Signal.get - reactive JSX expressions often become
Node.signalTextor other explicit reactive nodes - routing and SSR move from the Solid ecosystem to Xote's built-in modules
The underlying mental model stays largely the same.