Why I put React Native on Impeller

I wanted React Native on Linux without building a one-off. That led to a Skia platform first, and then to Impeller.

I wanted to run a React Native app on Linux, and there wasn’t a real way to do it.

On iOS and Android, React Native doesn’t draw your UI itself, since at runtime it creates the matching native Android and iOS views for your components.[1] Any other platform needs someone to build that layer.[2] The React Native docs list partner platforms for macOS and Windows, and they say building your own platform “is still not very well documented.”[2] The macOS version is a fork of React Native itself, maintained by Microsoft.[3] The only Linux option the docs list is a community project called React Native Skia, which “currently supports Linux and macOS.”[2]

Careful with that name, because two different projects use it.[4][5] The one most React Native developers know is a graphics library, “High-performance 2d Graphics for React Native using Skia.”[5] You put a <Canvas> in your app, draw shapes into it, and animate them with Reanimated on the UI thread.[6] It installs into iOS, macOS, and Android apps through CocoaPods and Gradle, so it needs a platform React Native already runs on.[7] The one in the docs is a “React Native renderer based on Skia,” an attempt at being the platform itself.[4]

I also don’t like building one-offs. If I was going to write a renderer for Linux, I wanted the same one to work on macOS and Windows. Skia looked like the answer. It’s an open source 2D graphics library with “common APIs that work across a variety of hardware and software platforms,” and it already draws Chrome, Android, and Flutter.[8] Its supported platforms include Windows, macOS, and several Linux distributions.[8]

A short history of React Native Skia

September 2020: The react-native-skia repo is created for a React Native renderer built on Skia.[9][4] Kudo Chien later presented it as a proof of concept for a new platform on React Native’s new architecture.[10] The same day the repo was created, he published version 0.0.1 of the react-native-skia npm package, described as “Temp prebuilt third-party-oss files.”[11]

December 2021: Shopify ships the first alpha of its own React Native Skia.[12] Confusingly, it’s the graphics library, a different thing with the same name, published as @shopify/react-native-skia.[12][13] It gets far more popular, with about 8,700 GitHub stars to the renderer’s 1,100.[14][9]

March 2023: The renderer gets its last commit.[15] Its README still says it “is still in proof of concept and requires lots of work.”[4] The React Native docs still point Linux users to it.[2]

September 2026: Shopify announces it’s moving its apps off React Native.[16][17] William Candillon will keep the Skia library going and publish it “under a new name.”[16] Shopify’s GitHub repo for it now redirects to his account.[18][14]

October 2026: The graphics library’s new name is react-native-skia, without the Shopify scope.[13] That npm name was published in 2020 from the renderer’s repo, and it never got past a 0.0.1 placeholder.[11][19] Now it holds the graphics library, and the version numbers pick up where Shopify’s stopped: @shopify/react-native-skia ends at 2.14, and react-native-skia jumps from 0.0.1 straight to 2.15.0 on October 2.[13][11] Kudo Chien is still listed as one of the package’s maintainers, which I’m choosing to read as a blessing.[11]

Confused yet? Me too! The React Native Skia that renders apps on Linux lives in one repo, and the npm package with its name now installs the graphics library.[4][13] Neither one got my app running on Linux.

Building a Skia platform

So I built my own React Native platform on Skia first.[20][21] The rendering worked, but the packaging didn’t. A comment in my current test suite says the Skia version’s packaging bugs survived nine phases before anything caught them.[22] I’m not proud of that one. None of it was reusable in the way I wanted.

Switching to Impeller

Then I found out about Impeller. Flutter built it to fix problems it had with Skia, and Impeller is now the only renderer on iOS and the default on Android, macOS, Linux, and Windows.[23][24] It also ships as a standalone SDK with a C API that “can be consumed by non-Flutter applications and projects.”[25]

I started the Impeller platform on August 9, and the first commit was a test that Impeller could render with no GPU at all.[20] This time the platform is a package you install next to stock React Native, the way React Native for Windows works.[26][27] The engine takes the app name and an absolute bundle path on the command line, so there’s no hardcoded app name and no path that depends on where you launch from.[28]

What Impeller does better

Here is what changed when I swapped Skia for Impeller, and why it suits a platform I want to reuse everywhere:

  • Google publishes a prebuilt Impeller SDK for every Flutter commit, for macOS, Linux, Windows, and Android, so I could start without building any renderer code at all.[25][29]
  • The core renderer is “less than 200 KB compressed,” and text layout with its ICU data brings it to about 2.5 MB.[25] The stripped Linux library I ship is 8.75 MB.[20]
  • Text layout and shaping come with the SDK by default.[25]
  • Impeller “precompiles a smaller, simpler set of shaders at engine-build time so they don’t compile at runtime.”[24]
  • The SDK performs best on modern GPU APIs like Metal or Vulkan and on the tiled GPUs in phones and Apple Silicon.[25]
  • Avalonia, a .NET UI framework now working on an Impeller backend, repeats numbers from Flutter’s Android rollout: complex clipping went from 450 ms a frame with Skia to 11 ms with Impeller, with about 100 MB less memory.[23] In Avalonia’s own test, Impeller and Vello both held 28 fps, but Vello used about twelve times the power.[23]
  • Impeller draws from a display list, which is a recorded list of draw calls, and my engine builds one from React Native’s view tree every frame.[30]

A view with rounded corners becomes one draw call:[31]

// Simplified from the scene graph:
// one Fabric <View> becomes one draw call.
if (rounded) {
  ImpellerDisplayListBuilderDrawRoundedRect(
      builder, &rect, &radii, paint);
} else {
  ImpellerDisplayListBuilderDrawRect(builder, &rect, paint);
}

Impeller isn’t better at everything, and one Flutter bug report found that “the average memory data of impeller is always higher than skia” while scrolling on Android.[32] It also needs a GPU, and its README says it “will likely never have a dedicated software renderer.”[25]

Needing a GPU was the biggest risk, because I didn’t want to pay for GPU machines just to run tests.[20] The first experiment drew shapes on Linux with no GPU and read 45 distinct colors back out of the frame.[20] When I changed a rounded rectangle to a square one, only the corner check failed.[20]

Following Flutter

Flutter is also a working example of how to run Impeller well, so I studied it. In October I went through Flutter’s engine source at the exact commit my SDK is built from and checked 335 citations to it.[33] I copied its threading: React Native’s JS thread takes the place of Flutter’s UI thread, and a separate raster thread does the drawing.[33] Before that change, a native-driven animation fell 49 to 53 frames behind when JavaScript stalled for 50 ms every other frame, and afterward it fell 0 frames behind.[33]

I also picked the same graphics backends Flutter ships on each OS:[26]

  • macOS: Apple’s Metal, with 4x multisample anti-aliasing.[26]
  • Windows: OpenGL ES through ANGLE on Direct3D 11.[26]
  • Linux: OpenGL ES over EGL.[26]
  • Android: experimental, Vulkan with an OpenGL ES fallback.[26]
  • iOS: not supported yet.[26]

Why not Vulkan everywhere?

Flutter ships OpenGL ES on Linux and on Windows through ANGLE, and Metal on Apple platforms.[26]

Moving the Mac from ANGLE to native Metal fixed frame pacing.[33] Under ANGLE the gap between frames had a standard deviation of almost 6 ms, and 30 of 360 frames came early.[33] On Metal the median gap matches the 16.68 ms refresh and the deviation is 1 microsecond.[33]

Jim, the AI workflow app I use every day, runs on this engine, and on the Mac it showed its first frame in 973 ms and had a 95th percentile frame time of 9.0 ms.[33]

Gluestack and NativeWind

Jim’s UI is built with Gluestack UI and styled with NativeWind.[34][35] Gluestack’s stated goal is “one codebase, same behavior across React, Next.js, and React Native.”[36] A stock React Native app with NativeWind now bundles and paints on Impeller with no Metro shims of its own.[34]

Getting there took fixes in the platform package.[35] Gluestack’s Popover exposed one: a few React Native files re-export themselves and expect an .ios.js or .android.js sibling, so on a new platform the default export comes back undefined.[35] The package’s Metro resolver handles those files now.[35] The test app has no Metro shims of its own, only withImpeller and withNativewind:[37]

const nw = withNativewind(withImpeller(getDefaultConfig(__dirname)), {
  inlineRem: 16,
  disableTypeScriptGeneration: true,
});

The test checks that a bg-red-500 box comes out as Tailwind’s red-500, exactly (251, 44, 54).[34] Another gate renders a grid of React Native style features, each next to a control, plus NativeWind classes in light, dark, and hover states.[38]

What’s missing

The platform is pre-release, at version 0.0.1 everywhere.[26] iOS isn’t supported yet, and no React Native runtime runs on Android yet.[26] I pin one Impeller commit, so every update is a deliberate, tested change.[29] I also ended up building the SDK from source with a couple of patches, even though I’d planned to only use the prebuilt one.[30][29]

The repo has 138 test scripts that compare rendered pixels, and 126 scripts that break the engine on purpose to prove a test can fail.[39][26]

The code is public on GitHub as react-native-impeller-platform.[40] If you’ve wanted React Native on Linux too, that’s where I’d start.

Sources

  1. Core Components and Native Components (React Native docs)
  2. Out-of-Tree Platforms (React Native docs)
  3. microsoft/react-native-macos README
  4. react-native-skia README
  5. wcandillon/react-native-skia README
  6. Animations (React Native Skia docs)
  7. Installation (React Native Skia docs)
  8. About Skia (skia.org)
  9. react-native-skia/react-native-skia (GitHub API record)
  10. Kudo Chien: React Native on Linux with the New Architecture (App.js Conf talk)
  11. npm registry record for react-native-skia
  12. React Native Skia: A Year in Review and a Look Ahead (Shopify Engineering)
  13. Migrating to v3 (React Native Skia docs)
  14. wcandillon/react-native-skia (GitHub API record)
  15. react-native-skia latest commit (GitHub API)
  16. Native is now the future of mobile at Shopify (Shopify Engineering)
  17. Shopify Left React Native. The CLI Is the Reusable Part. (TechCurrent)
  18. GitHub API: Shopify/react-native-skia now resolves to wcandillon/react-native-skia
  19. GitHub API: Kudo/react-native-skia now resolves to react-native-skia/react-native-skia
  20. P0: Impeller renders headless (react-native-impeller-platform commit, 2026-08-09)
  21. P1: packaging skeleton + consumer gate (react-native-impeller-platform commit)
  22. Gate runner (tools/ci-all.sh)
  23. Avalonia Partnering with Google's Flutter Team to Bring Impeller Rendering to .NET
  24. Impeller rendering engine (Flutter docs)
  25. Impeller Standalone SDK README (flutter/flutter)
  26. react-native-impeller-platform README
  27. React Native for Windows: Get Started
  28. Engine host CLI contract (engine/host/main.cpp)
  29. Impeller SDK fetch script (tools/sdk-fetch.sh)
  30. Flutter-parity rendering spec (docs/eng/flutter-parity/SPEC.md)
  31. Scene graph (engine/scenegraph/SceneGraph.cpp)
  32. [Impeller] Impeller uses more memory than skia when sliding the page (flutter#157497)
  33. Flutter-parity decisions and results (docs/eng/flutter-parity/DECISIONS.md)
  34. NativeWind gate (tools/ci-nativewind.sh)
  35. Metro resolver shims (package/src/metro-shims/index.js)
  36. gluestack-ui introduction
  37. NativeWind test app Metro config (fixtures/eng19-nativewind/metro.config.js)
  38. Style coverage gate (tools/ci-style-coverage.sh)
  39. tools/ directory (138 ci-*.sh gates, 126 mutation-verify-*.sh scripts)
  40. blbaker/react-native-impeller-platform on GitHub

About the author

Brandon Baker is a software engineer in Ames, Iowa. He builds react-native-impeller and runs Jim, his AI workflow app, on it.

hello@bbaker.dev

All writing