We Built the Same App in SolidJS and React: Signals vs Hooks
Last quarter, we had a client project that gave us the perfect excuse to do something we'd been itching to try: build the same application in both SolidJS and React, then compare everything. Not a todo app. Not a counter. A real production dashboard with authentication, real-time data updates, complex forms, and a charting module that renders thousands of data points.
The results surprised us. Not because one framework "won" -- but because the tradeoffs were far more nuanced than the Twitter discourse suggests. Some things we expected. Some things caught us completely off guard. This is what we actually learned.
Table of Contents
- The App We Built
- Signals vs Hooks: A Mental Model Shift
- Bundle Size Comparison
- Rendering Performance Benchmarks
- Developer Experience and Ecosystem
- Production Tradeoffs That Actually Matter
- When to Choose SolidJS Over React
- Code Comparison: Real Patterns
- FAQ

The App We Built
The application is a real-time analytics dashboard for an e-commerce client. Here's what it includes:
- Authentication flow with JWT tokens and refresh logic
- Dashboard with 6 widget panels, each pulling data from different API endpoints
- Real-time order feed using WebSocket connections
- Interactive charts rendering 5,000+ data points (using a charting library)
- Complex filter forms with dependent dropdowns and date range pickers
- Admin settings panel with nested state management
- Responsive layout with a sidebar navigation
Both versions connect to the same backend API. Both use TypeScript. Both use Vite as the build tool. We kept third-party dependencies as similar as possible -- same charting library (Chart.js), same HTTP client (ky), same WebSocket wrapper.
The React version uses React 19 with hooks. The SolidJS version uses Solid 1.9. We didn't use any meta-frameworks (no Next.js, no SolidStart) because we wanted to isolate the framework differences without routing and SSR muddying the comparison.
Signals vs Hooks: A Mental Model Shift
This is the big one. And honestly, it took our team about a week to stop writing React-shaped code in SolidJS.
How React Hooks Work
You know this, but it's worth stating explicitly for the comparison. React's model is: when state changes, the component function re-executes. The entire function. Every useState, every useMemo, every useCallback -- they're all mechanisms to work around the fact that the whole function re-runs.
// React: This entire function re-runs on every state change
function OrderFeed() {
const [orders, setOrders] = useState([]);
const [filter, setFilter] = useState('all');
// This recalculates every render unless we memoize
const filteredOrders = useMemo(() =>
orders.filter(o => filter === 'all' || o.status === filter),
[orders, filter]
);
// This ref gets recreated conceptually every render
const handleNewOrder = useCallback((order) => {
setOrders(prev => [order, ...prev]);
}, []);
useEffect(() => {
const ws = connectWebSocket(handleNewOrder);
return () => ws.close();
}, [handleNewOrder]);
return <OrderList orders={filteredOrders} />;
}
How Solid Signals Work
Solid's model is fundamentally different. The component function runs once. After that, only the specific expressions that read a signal re-execute when that signal changes. There's no virtual DOM diff. There's no reconciliation. The reactive graph tracks exactly which DOM nodes depend on which signals.
// Solid: This function runs ONCE
function OrderFeed() {
const [orders, setOrders] = createSignal([]);
const [filter, setFilter] = createSignal('all');
// This is automatically tracked -- no dependency array needed
const filteredOrders = createMemo(() =>
orders().filter(o => filter() === 'all' || o.status === filter())
);
// No useCallback needed -- this closure is stable
const handleNewOrder = (order) => {
setOrders(prev => [order, ...prev]);
};
// onMount instead of useEffect with empty deps
onMount(() => {
const ws = connectWebSocket(handleNewOrder);
onCleanup(() => ws.close());
});
return <OrderList orders={filteredOrders()} />;
}
What This Means in Practice
The Solid version has no dependency arrays. No useCallback. No useMemo for derived state (though createMemo exists for expensive computations -- the difference is it's opt-in for performance, not required for correctness).
Here's what actually bit us during development:
Destructuring props kills reactivity in Solid. We had a junior dev destructure props in a Solid component and spent 45 minutes debugging why updates weren't propagating. In React, destructuring is fine because the whole function re-runs. In Solid, you need to access
props.valueinside the JSX or a tracked scope.Early returns are tricky in Solid. You can't do
if (!data()) return <Loading />at the top of a Solid component the way you would in React. The component function only runs once, so that conditional would never re-evaluate. You need<Show when={data()}>instead.No stale closure bugs. This was the big win. In our React version, we had three separate stale closure bugs in the first week related to WebSocket handlers capturing old state. The Solid version had zero, because signals are always read at access time, not captured in a closure.
Bundle Size Comparison
We measured both production builds with the exact same Vite configuration, gzip compression, and code splitting strategy.
| Metric | React 19 | SolidJS 1.9 | Difference |
|---|---|---|---|
| Framework runtime | 44.2 KB (gzip) | 7.1 KB (gzip) | -84% |
| Total initial bundle | 127.8 KB | 89.3 KB | -30% |
| Total app (all chunks) | 312.4 KB | 274.1 KB | -12% |
| First chunk (critical path) | 68.4 KB | 41.2 KB | -40% |
| Number of chunks | 14 | 14 | Same |
Solid's runtime is dramatically smaller. That's not news -- Ryan Carniato has talked about this extensively. But what surprised us was that the total app size difference narrowed significantly once you add real application code, third-party libraries, and assets.
The framework runtime matters most for initial load. And at 7.1 KB gzipped, Solid basically disappears into the noise. React's 44 KB is noticeable, especially on mobile connections.
Tree Shaking
Solid tree-shakes better than React. Unused Solid primitives get eliminated entirely. With React, the reconciler and fiber architecture ship whether you use all their features or not. We confirmed this by building a minimal page in both -- Solid's floor is lower.

Rendering Performance Benchmarks
We ran structured benchmarks using Chrome DevTools performance profiles, Lighthouse, and custom timing instrumentation. All tests on an M2 MacBook Pro with CPU throttling set to 4x slowdown to simulate mid-range devices.
Chart Rendering (5,000 data points)
| Metric | React 19 | SolidJS 1.9 |
|---|---|---|
| Initial render | 142ms | 89ms |
| Re-render on filter change | 67ms | 23ms |
| Memory during render | 18.4 MB | 11.2 MB |
| DOM nodes created | 5,847 | 5,812 |
Solid was consistently faster on re-renders because it doesn't diff a virtual DOM tree. When a filter signal changes, only the expressions that read that signal update. React 19's compiler improvements helped -- the same test on React 18 was 95ms for re-renders -- but Solid still had a clear edge.
Real-Time Order Feed (100 updates/second)
This is where things got really interesting. We pushed 100 WebSocket messages per second into the order feed.
| Metric | React 19 | SolidJS 1.9 |
|---|---|---|
| Frame drops (per 10s) | 12 | 2 |
| Avg. paint time | 8.3ms | 3.1ms |
| Max paint time | 34ms | 11ms |
| CPU usage (avg) | 24% | 9% |
Solid absolutely crushed this benchmark. High-frequency updates are where fine-grained reactivity pays the biggest dividends. React was batching updates (which is good), but still diffing more DOM than necessary on each batch.
We should note: React 19's useTransition and automatic batching made this much better than React 18 would have been. And for most real-world apps, you're not pushing 100 updates per second. At 10 updates/second, both frameworks were smooth.
Complex Form with 40 Fields
| Metric | React 19 | SolidJS 1.9 |
|---|---|---|
| Keystroke input lag | 2-4ms | <1ms |
| Form submission render | 28ms | 12ms |
| Component re-renders per keystroke | 3-8 | 1 |
In React, typing in one field would cause parent components to re-render unless we carefully memoized everything. In Solid, typing in a field literally only updates that field's DOM text node. Nothing else re-executes.
Developer Experience and Ecosystem
Performance isn't everything. You have to actually build the thing, debug it, hire for it, and maintain it.
Ecosystem Size (as of early 2025)
| Factor | React | SolidJS |
|---|---|---|
| npm weekly downloads | ~28M | ~85K |
| GitHub stars | 233K+ | 33K+ |
| Stack Overflow questions | 470K+ | ~2K |
| UI component libraries | 50+ mature options | 5-8 options |
| Job postings (LinkedIn US) | ~45,000 | ~200 |
| Meta-framework | Next.js (mature) | SolidStart (beta) |
This is the elephant in the room. React's ecosystem is orders of magnitude larger. When we needed a date range picker for Solid, we had to either port a React one or write our own. In React, we had 15 options to choose from.
We used Kobalte for accessible UI primitives in Solid, and it's genuinely good. But it's no Radix UI in terms of component coverage.
Debugging
React DevTools is mature, well-maintained, and something every frontend dev knows. Solid has its own DevTools extension, and it's decent -- you can inspect the signal graph, which is actually more useful for tracking reactivity bugs than React's component tree view. But it's less polished.
TypeScript Support
Both are excellent. Solid's TypeScript support is actually slightly better for JSX because the compiler handles more at build time. We had fewer type gymnastics in the Solid codebase.
Production Tradeoffs That Actually Matter
After building both versions and deploying the Solid version to staging (the client ultimately chose React for production -- more on that below), here are the tradeoffs that actually mattered:
Hiring
Our client has a team of 8 frontend developers. All know React. None had used Solid. Training time estimate: 2-3 weeks for proficiency, 2-3 months for mastery. That's a real cost. This is the single biggest factor in most production decisions, and it's why we typically recommend React or frameworks like Next.js for teams that need to hire frequently.
Third-Party Library Compatibility
We had to write custom wrappers for three libraries that had React-specific integrations. This added roughly 20 hours of development time. In a React project, those hours don't exist.
Server-Side Rendering
SolidStart is promising but was still in beta during our project. Next.js is battle-tested. For projects where we need production-grade SSR with all the trimmings, we're still reaching for Next.js development or Astro depending on the content model.
Performance Where It Counts
Here's the honest truth: for this particular dashboard, both frameworks performed well enough for production. The Solid version was measurably faster, but the React version was never slow. Users wouldn't notice the difference in most interactions.
The exception was the real-time feed at high update frequencies. If your app has a use case like that, Solid's performance advantage is meaningful, not just benchmark bragging.
When to Choose SolidJS Over React
After this experiment, here's our honest framework for the decision:
Choose SolidJS when:
- Your app has high-frequency reactive updates (dashboards, trading platforms, real-time collaboration)
- Bundle size is critical (embedded widgets, micro-frontends, mobile-first)
- Your team is small and willing to invest in learning a new paradigm
- You want fine-grained reactivity without fighting the framework
- You're building something new and don't need a massive component library ecosystem
Choose React when:
- Your team already knows React (this alone is usually decisive)
- You need a mature meta-framework (Next.js, Remix)
- Third-party library availability is important
- You're hiring and need a large talent pool
- Your app's performance requirements are typical (not extreme)
For our headless CMS development projects, React still dominates because CMS integrations (Sanity, Contentful, Storyblok) have first-class React SDKs. Solid support exists but is often community-maintained.
Code Comparison: Real Patterns
Let's look at some real patterns from the project side by side.
Data Fetching with Loading States
React:
function Dashboard() {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchDashboardData()
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, []);
if (loading) return <Skeleton />;
if (error) return <ErrorBanner error={error} />;
return <DashboardGrid data={data} />;
}
SolidJS:
function Dashboard() {
const [data] = createResource(fetchDashboardData);
return (
<Suspense fallback={<Skeleton />}>
<ErrorBoundary fallback={(err) => <ErrorBanner error={err} />}>
<DashboardGrid data={data()} />
</ErrorBoundary>
</Suspense>
);
}
Solid's createResource with Suspense is genuinely elegant. React has Suspense too (and it's gotten better in React 19), but Solid's version felt more natural to work with. Less boilerplate, fewer state variables to manage.
Conditional Rendering
React:
{user ? (
<Profile user={user} />
) : (
<LoginPrompt />
)}
SolidJS:
<Show when={user()} fallback={<LoginPrompt />}>
{(u) => <Profile user={u()} />}
</Show>
Solid's <Show> component exists because ternaries don't work the same way when the component function only runs once. It took getting used to, but the callback pattern gives you a narrowed, non-null reference which is nice for TypeScript.
List Rendering
React:
{orders.map(order => (
<OrderRow key={order.id} order={order} />
))}
SolidJS:
<For each={orders()}>
{(order) => <OrderRow order={order} />}
</For>
Solid's <For> doesn't need keys because it tracks array items by reference. When an item moves in the array, Solid moves the actual DOM node. React would unmount and remount. This is why Solid's list performance is so good.
FAQ
Is SolidJS production-ready in 2025?
Yes. Solid 1.x has been stable for over two years. Companies like Cloudflare and Samsung have used it in production. The core library is mature and well-tested. The ecosystem around it (SolidStart, component libraries) is smaller than React's but growing steadily. The question isn't whether Solid is ready -- it's whether your team and project requirements align with its ecosystem size.
Can I migrate from React to SolidJS incrementally?
Not easily. Despite the similar JSX syntax, the runtime models are fundamentally different. You can't embed Solid components inside a React app (or vice versa) without iframe or web component boundaries. A migration would essentially be a rewrite. If you're considering it, start with a new isolated feature or micro-frontend rather than trying to convert existing React code.
Does SolidJS support server-side rendering?
Yes. SolidStart provides SSR, streaming, and server functions. It's functional and improving rapidly, but as of early 2025, it's still less mature than Next.js or Remix. For SSR-heavy projects, we'd still recommend evaluating Next.js or Astro depending on your content needs.
How does React 19's compiler compare to Solid's approach?
React 19's compiler (formerly React Forget) auto-memoizes components to reduce unnecessary re-renders. It's a significant improvement. But it's still working within React's model of re-running component functions and diffing a virtual DOM. Solid skips both of those steps entirely. The compiler makes React faster, but it doesn't change the fundamental architecture. In our benchmarks, React 19 with the compiler was about 30% faster than React 18, but still slower than Solid on update-heavy scenarios.
What about Preact Signals as a middle ground?
Preact Signals (and the @preact/signals-react adapter) bring signal-like reactivity to the React ecosystem. It's an interesting approach, but it's fighting against React's core model rather than working with it. We tested it briefly and found edge cases with Suspense and concurrent features. If you want signals, Solid gives you the full experience without the impedance mismatch.
Is SolidJS harder to learn than React?
If you already know React, Solid's API will look familiar -- similar JSX, similar patterns. The hard part is unlearning React's mental model. You'll reach for useEffect patterns that don't exist. You'll destructure props and break reactivity. You'll try early returns and wonder why they don't work. Budget about 1-2 weeks for an experienced React dev to become productive in Solid, and another month or two to stop writing React-flavored Solid.
Which framework has better TypeScript support?
Both have excellent TypeScript support. Solid has a slight edge because its compiler can provide narrower types in certain patterns (like the callback in <Show>), and you don't need the same volume of generic type parameters that complex React hooks sometimes require. But honestly, both are great. This shouldn't be a deciding factor.
Should I use SolidJS for my next project?
It depends on your constraints. If you're a small team building something performance-sensitive and you're willing to invest in learning curve and work around a smaller ecosystem, Solid is a genuinely excellent choice. If you need to hire React developers, use established component libraries, or need a battle-tested meta-framework for production, React is the safer bet. There's no universal answer -- only the right answer for your specific situation. If you want to talk through the decision for your project, reach out to us and we can help you evaluate the tradeoffs.