Status: these instructions describe the intended UX. Until the native runtime builds end-to-end,
pnpm react-native run-linuxwill fail partway through. See TODO.md for the gates.
- Ubuntu 22.04 LTS or 24.04 LTS (other distros: probably work, untested).
- Node.js 20 LTS.
- pnpm 9 (
corepack prepare pnpm@9.15.5 --activate). - GTK4 development headers + Hermes build deps (see below).
sudo apt install -y \
build-essential cmake ninja-build pkg-config \
libgtk-4-dev libglib2.0-dev \
python3 python3-pipnpx @react-native-community/cli init MyApp
cd MyApp
pnpm add -D @lucid-softworks/react-native-linux @lucid-softworks/react-native-linux-cli
pnpm react-native init-linux
pnpm react-native run-linuxinit-linux writes a linux/ directory to your project with a minimal CMake
project and a main.cpp that boots RNLinuxApplication. The generated
main.cpp + app.desktop are templated with values derived from your
package.json:
applicationId— the reverse-DNS GApplication id. Defaults toapp.lucidsoft.<PascalCasedName>(e.g.hello-world→app.lucidsoft.HelloWorld). Override withrnLinux.applicationIdinpackage.jsonwhen you need an explicit value (com.acme.MyApp).windowTitle— PascalCased package name (hello-world→HelloWorld).- Executable name — sanitized package name (
hello-world).
Two installed apps with different applicationId get disjoint on-disk
state (AsyncStorage JSON, SecureStore keyring entries, FileSystem
documentDirectory / cacheDirectory) — see
docs/design-multi-instance.md.
run-linux:
- Configures CMake (
cmake -B linux/build -G Ninja). - Builds with Ninja.
- Launches the executable.
- Expects a Metro instance on
127.0.0.1:8081— start that yourself withpnpm startin another terminal.
On launch the app:
- Reads
RN_BUNDLE_URL(or constructs one fromRN_METRO_HOST+RN_METRO_PORT). - Creates a
GtkApplicationWindowwith aGtkFixedroot. - Boots
RNLinuxHost, which:- Spins up the Hermes runtime on a JS thread.
- Downloads + evaluates the bundle.
- Mounts the root surface onto the
GtkFixed.
- Renders the React tree as native GTK widgets.
Environment variables read by the default main.cpp:
| Var | Default | Notes |
|---|---|---|
RN_BUNDLE_URL |
(unset) | Wins over RN_METRO_HOST/_PORT. |
RN_METRO_HOST |
127.0.0.1 |
Use the host's LAN IP for VM-hosted Metro. |
RN_METRO_PORT |
8081 |
Pass-through to Metro. |
You can edit linux/main.cpp freely — it's part of your app, not the
library.
pnpm react-native bundle-linux \
--bundle-output linux/build/assets/index.linux.bundle \
--assets-dest linux/build/assets \
--dev false --no-minify
cmake -S linux -B linux/build -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build linux/build
# The executable now expects `index.linux.bundle` next to it.
ls linux/build/rn-linux-app linux/build/index.linux.bundleOnce you have a working Release build, bundle it into a single-file AppImage:
scripts/package/appimage.sh \
--app-dir linux/build \
--executable rn-linux-app \
--desktop linux/app.desktop \
--bundle linux/build/assets/index.linux.bundle \
--output dist/rn-linux-app.AppImageThe script fetches linuxdeploy and appimagetool on first run, stages an
AppDir, copies in the JS bundle, and emits a runnable AppImage. Linux-only
— run it from inside the Lima dev VM if you're on macOS.
For Flatpak, Debian, and Snap targets see the "Distribution" section of TODO.md; they're tracked as stretch items.