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.
Not going to lie. I had Fable write most of this response. This has been a genuinely good read though, and I think it is narrowing down on the critical distinctions.
The framing the thread ends on — "which complexity do you want exposed to the programmer, and which inferred by the compiler?" — treats the two as equivalent, just relocated. I'd argue they aren't. Exposed complexity is inspectable complexity. Inferred complexity doesn't disappear; it becomes invisible until it misbehaves, and then the answer to "why did this update?" lives somewhere you didn't write.
This is why I'd push back on filing observability under "tooling/maturity, Solid for now," as if it's a lead that erodes. In Solid the dependency graph isn't a mental model or a build-time artifact, it exists at runtime as a data structure. In dev you can walk any node's sources and observers, walk the ownership tree, and subscribe to a diagnostics stream where the system names problems precisely (a pending async value read outside a tracking scope, a reactive write in an owned scope, work scheduled on a disposed owner) because it knows exactly where every node sits. It can do that because dependencies are captured by actual reads as they happen — a conditional read produces exactly the edges that exist right now. Deriving dependencies from what a closure lexically captures, however good the inference, is a build-time answer to a runtime question. That's not a maturity gap; it's a difference in what the system knows while your app is running. And I'd argue runtime knowledge is getting more valuable, not less — it's the substrate devtools, tracing, and increasingly AI-assisted debugging stand on.
On async I'll actually cite Dominic's own planning doc. Octane inherits React's
use()contract — a pending read throws and the body replays — and the compiler recovers parallelism by proving independence between fetches. The doc's own prior-art survey describes Solid as "parallel by model," and its benchmark target was reaching the parallel floor that Solid sits at by construction. Where the proof succeeds, great. But the doc is equally clear about where it can't: loops are excluded, dependent creations don't hoist, warm plans cut at any edge the compiler can't verify, and compiler-invisible promise shapes fall back to sequential behavior at runtime. "Warm less = slower but never wrong" is the right engineering call — but it means parallelism is an optimization whose applicability depends on whether your code's shape was provable, rather than a semantic you can rely on.And the state itself stays opaque either way. When suspension is a thrown exception, "pending" isn't a value anyone can ask about — it's observable only as "this subtree stopped." In Solid 2, async status is data in the graph:
isPendingandlatestare reads with real guarantees ([isPending(x), x()]observed together is atomic; a resolved value can never be observed as not-pending-yet-undefined), and pending-ness propagates through ordinary sync derivations of held values. That's what stale-while-refetch UIs, transitions, and optimistic updates are built out of — plain reactive primitives — and it's what lets tooling show which value is pending and what caused the refetch. Better-scheduled suspension is still suspension.On the architectural-scaling concession: I think the external-store argument gives the point away rather than winning it. "Domain state lives in Zustand/MobX/Valtio with components as subscription boundaries" — sure, React proved that works. But look at what that boundary is: a knowledge boundary. Causality can't be traced through the external store, and the framework's async/pending/transition semantics stop at the snapshot edge —
useSyncExternalStoreprevents tearing precisely by coarsening to snapshots. The scaling story becomes two reactive systems stitched at a seam, and the hard coordination problems live exactly at the stitch. In Solid the store is the graph: a fetch flows through a store through a derivation into the specific binding it updates, and you can follow it — and query its pending state — the entire way. To me, "the fix for component gravity is a second reactive system" is evidence that fine-grained reactive state is what actually scales. The question is just whether it's native or bolted on.Where I'll concede without a fight: day-one authoring.
const doubled = count * 2reads better thanconst doubled = () => count() * 2, and Solid people shouldn't pretend otherwise. I'd only note what the trade buys: reads-as-function-calls is the entire interop protocol. Reactivity crosses any function boundary, any file, any third-party library, with no compiler cooperation and no provability requirements — and the value in your hand is never secretly stale.So my version of the closing question: not "which complexity do you want inferred?" but "does the system's knowledge of your application survive to runtime — where you debug, where tools operate, and where behavior actually happens?" A compiler can approximate a lot from ordinary code, and Octane is the most serious attempt at that I've seen. But an approximation made at build time can't be interrogated at runtime, and I think that property — more than authoring ergonomics in either direction — is the one that compounds as applications get complex.