NOTICE: I've summarized the perspectives of Octane's and Solid's creators here, for anyone short on time: https://gist.github.com/aleclarson/829c10aa7d287944ef75e1c2a90bef06
I'm a React/Preact/Astro developer with an interest in other renderers. The two that have most caught my eye recently are Octane (from Dominic Gannaway of React Hooks / Svelte / Inferno fame) and Solid 2.
I asked ChatGPT to compare them with its own "Renderer DX Pillars", which I've shared below. This is its gut instinct, with very little hedging:
| Pillar | Winner | Why |
|---|---|---|
| State architecture freedom | Solid 2 | Reactivity is independent of components. Much less component gravity. |
| Reactive reasoning | Solid 2 | The dependency graph is explicit and causal. Easier to answer “why did this update?” |
| Derived state & synchronization | Solid 2 | Fine-grained derivation is the native model; less need to coordinate state manually. |
| Async/data flow | Solid 2 | This is basically Solid 2's flagship idea. Async being part of reactivity feels deeper than improving Suspense/hooks ergonomics. |
| Component authoring | Octane | Normal values, normal functions, familiar TSX, fewer Solid-specific semantic gotchas. |
| Escape hatches | Solid 2, narrowly | Solid's primitives compose very directly with imperative JS without pretending everything is a component concern. |
| Ecosystem interoperability | Octane | If its React-ecosystem compatibility ambitions work, this could be a huge advantage. |
| Tooling & debuggability | Solid 2 today | More mature and battle-tested. Octane's compiler creates more invisible machinery that tooling has to explain well. |
| Architectural scaling | Solid 2 | Its reactive model naturally lets domain/application architecture exist independently of rendering. |
So my gut scoreboard is:
Solid 2: 7 Octane: 2
But the two Octane wins are meaningful ones.
Its pitch seems particularly strong around:
“I want React's authoring experience without React's runtime/design baggage.”
You get something like:
function Counter() {
const [count, setCount] = useState(0);
const doubled = count * 2;
return <button onClick={() => setCount(count + 1)}>
{doubled}
</button>;
}That's extremely approachable.
Solid asks you to internalize:
const [count, setCount] = createSignal(0);
const doubled = () => count() * 2;which isn't hard, but it is a different programming model.
So for day-one DX, I wouldn't be surprised if Octane feels better.
Solid's advantages seem to accumulate as the application becomes less like:
some components with state
and more like:
a complex reactive application that happens to have a UI.
That's where signals outside components, ownership, fine-grained derivation, async graph semantics, stores, and independent domain state all start reinforcing each other.
My gut says the trajectory looks something like:
small app
Octane ██████████
Solid █████████
↓ complexity
large/stateful app
Octane ████████
Solid ████████████
Not because Octane necessarily scales poorly, but because Solid's abstraction seems closer to the actual problem being modeled once state relationships get complicated.
Escape hatches.
Octane could easily end up winning there if its compiler makes effects, refs, subscriptions and integration code extraordinarily pleasant.
And ecosystem interoperability could become an overwhelming Octane win if their compatibility layer is genuinely broad and reliable rather than “we have adapters for the popular demos.”
If I had to assign confidence rather than points:
- Solid strongly wins: state architecture, derived state, async, architectural scaling.
- Solid probably wins: reactive reasoning, escape hatches.
- Octane strongly wins: initial component authoring.
- Octane probably wins: ecosystem interoperability.
- Solid wins for now: tooling/maturity.
The biggest question I'd personally want answered before choosing between them is therefore not performance.
It's:
Does Octane's compiler actually eliminate the architectural drawbacks of component-centric reactivity, or does it mostly make that model much nicer to write?
If it's the latter, I'd expect Solid 2 to remain the stronger foundation for complex applications.
We agree about a lot of things. And the cross-pollination is real — but I'd correct the lineage on the specific design you're critiquing. The purely colorless model — pending reads throw, value types stay clean, no marker anywhere — landed in our signals repo in August 2023, before runes were announced (we folded that repo into the monorepo, but the commits are all still there). Resources in Solid 1 pointed the direction but were nullable, so I won't claim more than we shipped — the fully colorless contract is the 2.0 work. What the Svelte conversations actually convinced me of was removing transitions, not the coloring approach. And notably, Svelte still doesn't have the colorless model today:
awaitin a$derivedor a template is the color, written right there in the source. This design is ours and very deliberate — which is exactly why I want to defend it properly.Coloring is painful when color is static and viral —
async/awaitrecolors every caller in the chain. We erased the color at the read site instead. Every reactive read has one contract: it may not be ready, pending-ness propagates through derivation, and boundaries orisPending/latestabsorb it wherever you actually care. The payoff is that asyncness isn't load-bearing in your code's structure — swap a sync signal for an async one and zero downstream code changes. Put it in the type system and that refactor goes viral again. Readiness as a dynamic, queryable property is our answer to coloring. And I'd gently note the compiler only "knows" inside its horizon — your own doc ships a runtime guard for promise shapes that escape the analysis through props and foreign modules. Deterministic where it can see; runtime fallback where it can't. Same boundary we're both living with, we just put the contract on different sides of it.I'll also take the garbage collector analogy, because I think it points the other way. GCs won general-purpose memory management because the runtime owns the live object graph — reachability is a runtime property that static analysis provably can't recover. Every attempt to replace that with inference under familiar syntax (escape analysis, region inference) ended up as an optimization layered on a runtime system, never a substitute for it. The one static approach that genuinely worked, Rust, required a new, stricter language — and even Rust concedes the unprovable shapes back to the runtime: Rc, RefCell, Arc are all runtime mechanisms for exactly the cases the borrow checker can't see through. Static analysis where it can prove, runtime knowledge for the general case — that's the stable equilibrium in memory management, and I'd bet it's the stable equilibrium here. Which is, as far as I can tell, exactly what "use strong" is walking toward: a genuinely stricter language. I think that's a coherent, even exciting bet — but notice what it spends: the moment the compiler hard-rejects React habits with no override, "your React knowledge transfers" stops being the pitch. At that point we're both asking people to learn a new model, and the comparison becomes: where do the semantics live — in a specified runtime graph you can interrogate while the app runs, or in a compiler's analysis you trust and read diagnostics from? Your own list of remaining work ("explain what the compiler decided and connect that explanation to what happened at runtime") is that exact gap, described from the other side.
On agents we mostly agree, but the argument cuts both ways. Yes, agents make stricter compile-time contracts practical — they'll happily refactor against a precise diagnostic. They also make day-one authoring familiarity matter much less, and when they're debugging a running application they ask runtime questions: why is this pending, what is this transition waiting on, why did this update twice. A diagnostic explains what the compiler couldn't prove; it doesn't explain what the app just did. I don't think compile-time and runtime knowledge are rivals — we compile too, obviously. The difference is only what gets discarded before the app runs.
No perfect async model, agreed — that's not the claim. The claim is narrower: when the model inevitably misses something, I want the miss to be inspectable in a specified, running graph rather than reconstructed from generated output. That's the property I think compounds.
Genuinely looking forward to watching "use strong" evolve. If the endgame is that both of us abandon "familiar" as a value and compete on which new model earns its constraints — that's a much better framing than the one this thread started with.