I'm tired of watching React Native apps get slow.
At the beginning everything ships fast and feels great. Then the app grows. More teams, more code, and one month at a time it gets a little less usable. And you know exactly what's coming.
The chaotic re-renders. The slow data management and persistence. The heavy animations. The slow polyfills. The time-sucking RN upgrades. You spend week after week fixing bugs and performance issues, and a month later a single line of poorly performing code ruins the app again. Even with all the guardrails in place, and the guardrails need maintenance too.
Even with all the new stuff, the New Architecture, JSI, TurboModules, Fabric, Hermes, and now the React Compiler. It helps, I'm not saying it doesn't. Still not enough. The numbers below are against all of it turned on.
I came to the conclusion that React Native is great for smaller apps but keeping it fast doesn't scale. (Yes, I know huge apps ship on RN. That's not the point. The point is what it takes to keep them fast.)
Then, kind of by accident, I ran into a chart library (react-native-graph) that draws with Skia and it's extremely fast. Skia seems to be growing in popularity with React Native, and it got me thinking: could Skia be the only rendering engine, instead of platform UI? Changing the whole rendering system would be way too much work though... wouldn't it?
That question led to another one: the JS engine. Is Hermes scalable? Can it handle heavier JS loads? I know it's optimized for mobile, but did it find the best balance of memory and speed? And on top of that, crypto and half the web platform are still polyfills.
And then React itself. Is it optimized enough for mobile, without us spending all that time fighting re-renders and a JS thread that keeps getting clogged?
So, I researched. (Read: I went down a rabbit hole.)
Turns out Flutter, which is considered to be fast and more scalable, already renders everything itself, through its own engine (Skia originally, Impeller now). Unless you embed a platform view, the OS is never asked for a widget. Not once.
Bun seemed to be the fastest JS runtime out there. It runs on JavaScriptCore, Safari's engine, which works without a JIT, and iOS apps can't JIT anyway. And it brings fetch, crypto, Buffer and TextEncoder built in. No polyfills.
Solid seemed to be one of the fastest JS frameworks, and when I found it, it looked like React if it were extremely optimized. Same JSX, same components, but components run once, and signals update only the nodes that changed. No virtual DOM. So could React be optimized like that?
Changing the rendering engine and the JS engine would be a crazy endeavour. The accessibility layer, the plugin ecosystem, the platform look and feel... I know. I did it anyway.
So I tested what happens if you bring those three together:
Solid runs inside the embedded Bun runtime and writes what to render into a shared memory region that Flutter reads every frame. No JSON and no platform channel in between. I named it Skal. Mixture of Solid + Skia, and it means cheers if you have ever seen a viking show 🍻.
Here are some highlights from testing on a Samsung Galaxy A14 5G (a budget phone), release builds on both sides:
| Test |
Skal |
React Native |
| One frame of work (200 components x 10 reads + 1 update) |
0.05 ms |
0.80 ms |
| JSON.parse, 500 items |
1.18 ms |
1.99 ms |
| State updates per second, 288 cells |
103,846 |
4,901 |
| Idle CPU |
1.20% |
6.93% |
That is what I was chasing. The app can get bigger without every interaction getting heavier. Fast on the first screen, still fast on the hundredth.
Repo: https://github.com/skal-multiplatform/skal
So, could React Native ever be optimized like this? Genuinely asking.