Javascript Libraries

Compare React vs. Solid: Reactivity, Developer Experience, and Performance

Kuntal Banerjee
CONTENTS

You’re about to embark on developing a SaaS platform. Your first task is to decide on your JavaScript UI framework, but each choice feels destined to set your organization up for costly rewrites before you launch an MVP or saddle yourself with years of tech debt as you struggle to reach your next big milestone.

We won’t claim to have solved the problem for you, but we can walk you through how Scalekit decided on Solid vs. React as the “de facto” standard and give you a simple plan for finally making a similar decision.

We’re not going to make any grand claims that Solid is perfect for all business cases, but we can say with confidence that Solid is a very strong choice for client-side rendered (CSR), single-page applications (SPAs) that must be enterprise-ready.

The beginning: a single working principle

When we began architecting Scalekit, we asked ourselves about our working principle: Where and how will this application be used in the next 18 months? Our answers guided all future decisions.

  • Where? Based on the business we were building—an enterprise-ready authentication platform for B2B—we could make important assumptions about the future usage of the SaaS. We felt it would be very unlikely for application developers or IT administrators to use Scalekit on mobile devices, and we had no plans on embedding the Scalekit administration portal within another application. Same went for using an iframe or allowing our customers to embed Scalekit in their own products.

Almost all usage would happen on company-supplied laptops/desktops in a relatively static environment, which meant we could deliver Scalekit with CSR in a single-page experience.

  • How? We knew Scalekit’s users would primarily interact with our platform to manage customers, administer their domains, change SSO configurations, and perform other tasks related to enterprise authentication quickly, which led us to prioritize performance and state management in as small of a client-side bundle as possible.

Scalekit also needed to be enterprise-ready—every view must be well-designed and accessible to users of all needs and devices. We had no leeway for buggy code that could lead to the exposure of personally identifiable information (PII) or data kept in memory an attacker could exploit later.

We chose 18 months as our timeframe because we knew so much could change within the business, the ideal customer profile (ICP), and the B2B authentication industry as a whole. We also weren’t trying to roadmap out years of features and architect a platform that could support them all—if we distracted ourselves with v2 before we'd even begin on v1, we would have led ourselves astray from that working principle.

Comparative summary of React vs. Solid

Let’s get straight to the point.

After exploring the features and paradigms of both frameworks, and tallying which were most important to our processes and experience, we rendered the details down into the simplest terms possible:

Feature
React
SolidJS
TypeScript support
Declarative nature
Unidirectional data flow
JSX first-class support
Direct manipulation of the DOM
x
Avoids component re-rendering
x
Highly performant
x
Rich community and ecosystem
x
Excellent developer documentation
Scaffolding tools
Conditional rendering
Server-side rendering (i.e., hydration)
Concurrent rendering (i.e., suspense)
No items found.

Of course, this is just a fraction of the technical problem-solving and structured decision-making we did along the way—for all that and more, keep reading.

The decision and implementation: major choices and minor caveats

We investigated the most popular JavaScript UI frameworks: React, Vue, Solid, Svelte, and Angular. After an initial evaluation of the broader ecosystem, we narrowed our focus to React vs. Solid for a handful of reasons most relevant to our working principle.

Reactivity via state management

Reactivity is how easily you can maintain consistency between the UI and your app's underlying data and state. A simple example of reactivity is that if your app displays a simple equation, like a = b + c, then a reactive app is one that instantly reflects any change in b or c to the state of a.

Despite React’s name, we found Solid’s primitive-level support for reactivity far better served our needs for client-side routing and speed.

React

React is non-reactive by default because it lacks a built-in mechanism for tracking changes in the data model and updating both behavior and view. Developers circumvent this by invoking state mutation calls and encapsulating side effects, which trigger React to reconcile the new freshly-updated virtual DOM against the actual DOM. We found that React uses more memory and costs more computational load for a few reasons:

  • React uses a reconciliation algorithm called Fiber to create a diff between the virtual DOM and real DOM. Fiber breaks reconciliation into small incremental steps, constantly comparing the virtual DOM with new state changes and deciding the best way to update the real DOM.

To identify changes, Fiber must constantly traverse the entire DOM tree, looking for changed properties and elements, both of which are computationally expensive tasks. Finally, Fiber attempts to identify the minimal set of DOM mutations required to reflect the new state, which takes even more CPU cycles. In our testing and public benchmarks, this constant reconciliation adds up tangibly in the end-user experience.

  • When React re-renders a component, it also renders the entire component tree beneath it, creating even more complex reconciliation steps.
  • When you pass callbacks as props, React often unnecessarily re-renders the child component. We were aware of an avoid unnecessary re-renders by memoizing a component or using the useCallbacks() hook, but felt that added to much additional development work across our entire app.

Solid

In contrast, Solid has first-class support for reactivity through its signal and subscriber primitives.

A signal is any dynamic data capable of being changed, like an incremental counter, through a getter and setter, which access the current value and adjust said value, respectively. In the below example, count() is the getter and setCount() is the setter:

function Counter() {
  const [count, setCount] = createSignal(0);
  const increment = () => setCount((prev) => prev + 1);

  return (
    <div>
      <div>Count: {count()}</div>
      <button type="button" onClick={increment}>Increment</Button>
    </div>
  )
}


Subscribers track changes in signals and update your app accordingly. You can create a subscriber with the createEffect() function, triggered when any signal depends on change. In the example below, the effect subscribes to the count() signal and executes the function, which multiplies the value with each change:

function Counter() {
  const [count, setCount] = createSignal(0);
  const [tenXCount, settenXCount] = createSignal(0);

  const increment = () => setCount((prev) => prev + 1);

  createEffect(() => {
    settenXCount(count() * 10);
  });

  return (
    <div>
      <div>Count: {count()}</div>
      <div>10X count: {tenXCount()}</div>
      <button type="button" onClick={increment}>Increment</Button>
    </div>
  )
}

Of course, you can use Solid’s signal and subscriber primitives to update the DOM directly. Unlike React, Solid doesn’t use a Virtual DOM to diff and reconcile state changes, opting for a built-in compiler that updates DOM nodes directly to avoid updating even a single component. By avoiding unnecessary and costly recalculations, often of an entire component tree, Solid offered us more flexibility and far better performance—on par with vanilla JS, an element we’ll get back to shortly.

Simpler component and data management

We knew reactivity was our must-have feature, so we needed a JavaScript framework that allowed for complex reactivity with minimal architectural effort.

In short, React’s lack of built-in state management implementation left us wanting.

React

First, advanced state management in React typically requires layering in another dependency like Zustand, Redux, or MobX. We’re not opposed to any of these great open-source libraries, but questioned the value we’d get from the larger dependency tree, bundle size, and additional complexity over using “vanilla” React.

Second, working around React’s primitives to achieve the reactivity we needed meant that data ruled the show. To effectively manage our app’s state, we compromised on how we curated our data, forcing us to create more convoluted component trees.

Solid

Solid’s built-in state management system, using those signal and subscriber primitives, let us detach worries about state and data from specific components. Instead of organizing components around our data, we built our component tree more logically around UI requirements and a familiar hierarchy our team could work with efficiently.

Availability of proven component libraries

As a product, Scalekit is all about helping developers at B2B organizations quickly add incredibly intricate features like SSO, magic links, and MFA within a single sprint. We believe that developers shouldn’t be the bottleneck in acquiring enterprise customers and that their time is better spent improving and scaling the product, not building complex SAML implementations.

Similarly, we didn’t want to spend engineering cycles re-inventing complex UI/UX components like date pickers to be visually consistent and accessible to all audiences—we needed a shortcut to a proper, enterprise-ready UI.

There is less of a conclusive win here; just that both UI frameworks are supported by powerful component libraries that let you architect your path toward MVP with flexibility.

React

Unsurprisingly, almost all popular component libraries already support React, and feature a large selection of ready-to-use, accessible components you can customize with brand colors, typography, and more. These component libraries also work with one or more CSS frameworks.

Whether you choose to pair a React app with React Aria, Material UI, Mantine, daisyUI, or any of the others, you can do so with confidence that you won’t need to waste time building yet another accordion on your way to MVP.

Solid

The lack of compatible and production-ready component libraries hampered our initial validation of Solid, which revealed that many options were React-only.

The saving grace here was ArkUI, which supports Solid, React, and Svelte. Aside from being an excellent component library, support for Solid gave us much-needed confidence that we could pair them in production. In addition, ArkUI also supports multiple CSS frameworks, including Panda CSS for those who prefer CSS-in-JS or Tailwind CSS for a utility class-based approach.

ArkUI’s component architecture was familiar based on our experience with React, and worked seamlessly with Solid’s state management primitives for reliable reactivity. For example, the Select component let us quickly create styled user-selectable dropdown menus that worked seamlessly with the rest of the app’s state.

import { createSignal } from 'solid-js'
import { Index, Portal } from 'solid-js/web'
import { Select } from '@ark-ui/solid'

interface Item {
  label: string
  value: string
  disabled?: boolean
}

export const Controlled = () => {
  const [, setSelectedItems] = createSignal<Item[]>([])

  const items: Item[] = [
    { label: 'React', value: 'react' },
    { label: 'Solid', value: 'solid' },
  ]

  return (
    <Select.Root items={items} onValueChange={(e) => setSelectedItems(e.items)}>
      <Select.Label>Framework</Select.Label>
      <Select.Control>
        <Select.Trigger>
          <Select.ValueText placeholder="Select a Framework" />
        </Select.Trigger>
        <Select.ClearTrigger>Clear</Select.ClearTrigger>
      </Select.Control>
      <Portal>
        <Select.Positioner>
          <Select.Content>
            <Select.ItemGroup>
              <Select.ItemGroupLabel>Frameworks</Select.ItemGroupLabel>
              <Index each={items}>
                {(item) => (
                  <Select.Item item={item()}>
                    <Select.ItemText>{item().label}</Select.ItemText>
                    <Select.ItemIndicator></Select.ItemIndicator>
                  </Select.Item>
                )}
              </Index>
            </Select.ItemGroup>
          </Select.Content>
        </Select.Positioner>
      </Portal>
    </Select.Root>
  )
}

Vanilla JS-like bundle size and performance

Performance is a top requirement of our working principle, so we also investigated how React vs. Solid performed in the end user’s browser.

Once again, React lagged in this area by quite a margin.

Bundle size

The volume of code delivered to the browser is a significant indicator of its weight, which allowed us to make more informed decisions about how much JavaScript React and Solid were loading. We investigated a “non-trivial Movies app” built in multiple frameworks, and examined the bundle size for each.

Framework
Demo
Size (Initial Load)
Solid
https://solid-movies.app/
13.5kB
Next (React)
https://movies.nuxt.space/
121kB
Nuxt (Vue)
https://movies.nuxt.space/
121kB
Angular
https://angular-movies-a12d3.web.app/list/category/popular
108kB
Svelte
https://sveltekit-movies.netlify.app/
25.6kB
Lit
https://lit-movies-2.netlify.app/
122kB

With an order of magnitude between the Solid vs. React bundle sizes, we had a clear winner. But what about the speed of the app in terms of load and re-render times during large changes in state?

Performance benchmarks

Instead of inventing our own JavaScript benchmarks, we leveraged the active and comprehensive js-framework-benchmark project. The testing suite creates a large table with randomized entries and measures how long each framework takes to manipulate data, edit the DOM, and re-render the end-user experience.

This project allows you to run benchmarks locally, but also publishes an official list of results to weigh any of the 100+ included frameworks against one another. Once you’ve selected which frameworks to compare, you can review a table with recent and consistently-applied tests

Comparing vanilla JS with Solid v1.8.0 and React v18.2.0, using both Hooks and Redux v.8.0.5, we saw an obvious advantage in Solid’s favor. Here are a few highlights:

  • Creating 1,000 rows: 37.3ms (Solid) vs. 52.3ms (React + Redux)
  • Clearing a table of 1,000 rows: 13.5ms (Solid) vs. 25.8s (React Hooks)
  • Time to first paint: 55.6ms (Solid) vs. 234.1ms (React + Redux)

💡 If you’re interested in seeing the results we investigated for yourself, copy the following results table, head over to the interactive results and press the paste button.

{"frameworks":["keyed/react-redux","keyed/react-redux-hooks","keyed/solid","keyed/solid-store","keyed/vanillajs"],"benchmarks":["01_run1k","02_replace1k","03_update10th1k_x16","04_select1k","05_swap1k","06_remove-one-1k","07_create10k","08_create1k-after1k_x2","09_clear1k_x8","21_ready-memory","22_run-memory","23_update5-memory","25_run-clear-memory","26_run-10k-memory","41_size-uncompressed","42_size-compressed","43_first-paint"],"displayMode":1}

Decisions and drawbacks on developer experience and beyond

Despite Solid’s clear wins in reactivity, state management, and performance, we also needed to validate that we were choosing a framework that wouldn’t hinder our velocity, had a vibrant community and ecosystem of educational content, and would not be abandoned by maintainers and sponsors in the foreseeable future.

This is an area where, based on your organizations’ working principle for architecting a SaaS, React is much more likely to win.

  • Community and ecosystem: React has an enormous community of thousands of developers, educators, and troubleshooters who can get you out of any bind. You’ll never struggle to onboard new React developers or find a contractor who can jump in to quickly ship a feature your in-house team doesn’t have experience with. If you’re struggling with React, chances are someone’s already asked and answered the question on Reddit or StackOverflow.

React also wins by a large margin on the many ways you can arrange component libraries, CSS frameworks, and third-party services/APIs directly into your app.

During our validation, we absolutely worried about Solid’s small ecosystem in comparison. Still, we found more than enough helper libraries to meet our working principle, and Solid’s documentation and educational content proved more than enough for us to familiarize ourselves with its reactive primitives and be productive. Because the window for our working principle was only the next 18 months, we couldn’t let ourselves worry about hypothetical roadmaps around features, funding, or hiring.

  • Server-side rendering (SSR): If your working principle requires server-side components, you should stick with Next.js+React or Nuxt+Vue instead of Solid… for now.

SolidStart is a release candidate-stage meta-framework developed by the Solid team. When the team releases v1.0, which will allow you to render a Solid app with CSR, SSR, streaming SSR, or static site generation (SSG), it may very well be a strong contender to the likes of Next.js and Nuxt.

The lack of SSR in Solid by default was not an issue for our team, as we already determined we would deploy a strictly client-side SPA.

  • Developer experience (DX) and adaptability: We worried we would struggle to adapt to a Solid-based developer experience because of our team’s deep familiarity with React. Instead, we found that a vast majority of component development was identical due to the common reliance on JSX.

Creating state within components was also quite similar, with the main difference being the method name—useState() vs. createSignal(). One major advantage of Solid is that each component function executes only once, removing any need to use hooks and saving worry over stale closures.

There were situations where Solid’s principle of executing a component function only once forced us to adapt from the React-driven patterns we were used to. You can often take advantage of the fact that React re-evaluates the entire component functions in response to a side effect which leads to other assignment and function invocations being re-evaluated which is outside (and above) the component’s JSX. That can be very useful if you are, for example, using the current pathname to evaluate some business logic. In Solid, only the JSX is re-evaulated by the compiler, so some of our “React way” of coding and debugging didn’t work. With solid, we adapted to using createEffect, untrack, signals, and stores using the Solid-specific design and composition patterns.

  • Past experience: The engineers responsible for this decision and implementation had years of experience building React-based SPAs for multiple use cases and end users. While this played a role in our validation process, we ultimately felt as though we would gain more advantages toward our guiding principle with Solid than we would lose in the process of adapting to a different DX.

Your turn: What’s next on your JavaScript journey?

Decision fatigue is real. The longer you architect your next JavaScript app, and the deeper you get into researching and validating each possible solution, the more each one seems lined by pitfalls and failure points.

If you’re facing a similar decision, start by defining your working principle: Where and how will your application be used in the next 18 months?

Whether your top priority is reactivity/state management, which drove us at Scalekit toward Solid, or code conciseness that might drive you toward Svelte, map the core features of each framework to that working principle. If that doesn’t clarify your answer, you can also look at the framework’s general popularity, the depth of its ecosystem, and your team’s expertise.

In the meantime, check out some additional resources for helping you choose the best framework for your JavaScript app:

Regardless of the framework you choose, Scalekit is ready to help you make your new SaaS enterprise-ready with SSO, multi-factor authentication, and much more. We have all the APIs, SDKs, and embeddable UI components you need to quickly test authentication in QA/staging and safely and securely ship to production, making your path to enterprise-level authentication and user experience one that’s simply peerless.

Ship Enterprise Auth in days

Ship enterprise auth in hours

Integrate SSO in a few hours

Add major identity providers instantly. Support SAML and OIDC protocols
Talk to our team today to:
Get a personalized demo of Scalekit
Learn how to add SSO to your SaaS app
Get answers for technical integration and setup

Integrate SSO in a few hours

email icon

Talk to you soon!

Thank you for signing up for our early access. Our team will be in touch with you soon over email.
Oops! Something went wrong while submitting the form.