Development Workflow
This guide covers the complete development workflow for Sparkling apps: starting the dev server, connecting from a simulator or real device, using hot reload, and managing the dev server URL. We use Sparkling Go (the official demo app) as the running example, but everything here applies to any Sparkling project.
The core idea: an HTTP dev server on your machine serves JS bundles to the native shell, with hot updates pushed on every file save. No manual rebuild needed.
Starting the dev server
Start the server
The dev server starts on port 5969 (spells LYNX on a phone keypad: L=5 Y=9 N=6 X=9).
Each entry point defined in app.config.ts is served as an HTTP bundle:
You can override the port with --port:
Run the native shell
In a separate terminal:
Debug builds automatically connect to the dev server. The app loads
main.lynx.bundle from the dev server URL instead of from bundled local assets.
Edit, save, see changes
Save any source file. The dev server rebuilds the affected bundle and pushes a hot update to the running app — no manual reload needed.
QR code for physical devices
When the dev server starts, it prints a QR code in the terminal. Scan it with a device that has LynxExplorer or your Sparkling app installed to load the bundle directly.
The QR URL is customizable via the pluginQRCode plugin in app.config.ts:
Hot Module Replacement (HMR)
Sparkling uses Rspeedy (built on Rsbuild/Rspack) as its bundler. HMR is enabled by default in dev mode — no configuration required.
How it works
- You save a source file.
- Rspeedy detects the change and generates a
.hot-update.jspatch. - The patch is served over HTTP to the running app.
- The Lynx runtime applies the patch. Changed components re-render.
State-preserving updates
For React state-preserving updates, add the pluginReactLynx plugin (included in the
default template):
With this, editing a component preserves its React state — only the changed component re-renders, without resetting the page.
Entry point changes
When you add, remove, or rename entries in app.config.ts, the dev server
automatically restarts. Other config changes (output settings, plugins) require a
manual restart.
Connecting from a real device
On a simulator, localhost just works. On a real device, the app needs your
machine's LAN IP address. sparkling run:android uses that LAN IP for physical devices.
If automatic detection picks the wrong network, use --host <host> or set
dev.server.host in app.config.ts.
The problem
The dev server runs on your machine at (e.g.) http://192.168.1.100:5969/. But
__webpack_public_path__ is baked into the bundle at build time as
http://localhost:5969/. When the app calls navigate() to load a sub-page, it
builds the URL from __webpack_public_path__ — pointing at localhost, which doesn't
exist on the device.
The solution: runtime URL resolution
sparkling-navigation automatically resolves this. When you call navigate() or
open(), it reads the actual URL the native side used to load the current bundle
from lynx.__globalProps.queryItems.url, and uses that as the base for all sub-page
navigation.
This happens automatically — no code changes needed in your app.
Managing the dev server URL
Sparkling provides two ways to set the dev server URL on a device: the in-app Settings UI (recommended for Sparkling Go) and the pipe API (for custom integrations).
Option A: In-app Settings UI (Sparkling Go)
Sparkling Go includes a Dev Server card on its Settings page. It only appears in
dev builds (__DEV__ === true).
Connection status
The card reads the runtime bundle URL from lynx.__globalProps (no pipe call needed)
and shows a live indicator:
Editing the URL
The Configured URL field shows the persisted dev server URL. Type a new one and the UI gives you inline feedback:
Save feedback
Tapping Save persists the URL on the native side. The result is shown inline:
Edge cases
Option B: Pipe API (custom integration)
If you're building your own app (not using Sparkling Go's Settings page), you can call the pipe methods directly to read and write the persisted dev URL.
Prerequisites: sparkling-debug-tool must be integrated in your native project with
getDevUrl and setDevUrl pipe methods registered.
This follows the shared Sparkling Method response codes.
The native side stores the URL in:
- iOS:
UserDefaultswith keysparkling.debug.dev_url - Android:
SharedPreferenceswith keydev_url
Automatic fallback dialog
If the app fails to load a bundle from the dev server (e.g. wrong IP, server not
running), the template app automatically shows a native dialog prompting you to enter
the correct dev server URL. This is handled by DebugDevURLSupport in the native shell
— no JS code is involved, since the bundle hasn't loaded yet.
Debugging features
SparklingDebugTool.setup() (iOS) / .init(application) (Android) turns on the basic
debug flags (lynxDebugEnabled, devtoolEnabled, logBoxEnabled). This enables
LogBox (the error overlay) and the Lynx debug runtime flags.
Sparkling's own in-app inspector is the Debug Panel. In debug builds, tap the
bottom-left sparkling debugTag to open it. See the Debug Panel guide
for screenshots of the entry point and each panel tab.
For the full Lynx DevTool experience (element inspector, JS debugging, network
monitor), you need to complete the additional integration steps described in the
Lynx DevTool guide. The key
missing pieces are enableAllSessions, setLogBoxPresetValue, and the JS bridge
loaders — without these, the DevTool desktop app cannot connect to your running app.
Troubleshooting
App shows a white/blank screen
- Is the dev server running? Check
npx sparkling devis active and shows "ready". - Can the device reach the server? Verify both are on the same network. Try
curl http://<your-ip>:5969/main.lynx.bundlefrom the device or simulator. - Wrong IP? If you changed networks, the IP may have changed. Update the dev URL
in Settings or restart
sparkling dev.
Sub-page navigation fails on real device
The navigate() function should automatically resolve the correct dev server IP. If it
doesn't, check that:
sparkling-navigationis up to date (needs thegetDevServerBaseURL()fix)- The native side passes
url=in the scheme's query parameters
HMR not working
- Check the dev server terminal for build errors.
- Some changes (new entry points, config changes) require restarting
sparkling dev. - Verify the app loaded from the dev server (check for "Connected to dev server" in
Settings, or look for
http://in the runtime URL).
"Pipe methods not available" in Settings
The getDevUrl / setDevUrl methods are provided by sparkling-debug-tool. Ensure:
sparkling-debug-toolis in your dependencies- The native side calls
SparklingDebugTool.setup()(iOS) or.init(application)(Android) during app initialization - The pipe methods are registered (check native build logs for registration errors)








