Capacitor Live Reload on a Physical Device Over Your LAN
Point a Capacitor app at your machine's LAN dev server so saved web changes appear on a real iPhone or Android device without a native rebuild.
04 Apr 2026, 20:11 UTC

What you get, and what has to be true first
The goal: the native app installed on a physical iPhone or Android device loads the web bundle from a dev server running on your computer, so saving a file in the web source updates the device screen without a native rebuild. Capacitor supports this through the server.url option in its config file — instead of loading bundled assets from the WebView's local origin, the WebView navigates to an HTTP URL you choose.
Two conditions must both hold: the dev server must be reachable from the device over the LAN, and the platform must permit cleartext HTTP to that address. Nearly every failure is one of those two.
Prerequisites and version assumptions
- Host and device on the same Wi-Fi or LAN segment, with client isolation (AP isolation) disabled. Guest networks and many corporate VLANs block device-to-device traffic.
- The dev server bound to the LAN interface, not just loopback. Vite binds to localhost by default, so run
npm run dev -- --hostor setserver.hostinvite.config.*. Ionic CLI's equivalent flag may differ by version — checkionic serve --help. - Host firewall allows inbound TCP on the dev port (commonly 5173 for Vite, 8100 for Ionic CLI).
- Capacitor CLI and
@capacitor/cliinstalled in the project, plus Xcode or Android Studio with a device attached.
Version numbers quoted in tutorials (Xcode 15, Android Studio Koala, Capacitor 5) are historical context, not requirements. Run npx cap doctor and compare against the current Capacitor documentation for the version you actually have installed.
Step 1 — Find the host LAN IP and prove reachability
Run on the host machine:
# macOS (Wi-Fi)
ipconfig getifaddr en0
# Linux
hostname -I
# Windows (PowerShell)
ipconfig
Take the IPv4 address on the same subnet as the device, for example 192.168.1.42. Before touching Capacitor, open the device's browser and navigate to http://192.168.1.42:5173. If the app loads there, networking is solved and any remaining problem is platform configuration. If it does not load, fix the server binding or the firewall first — no Capacitor setting will help.
Step 2 — Point Capacitor at the dev server
In capacitor.config.ts, add a server block. Keep this as a local development override; the committed config should keep loading bundled assets.
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'com.example.app',
appName: 'Example',
webDir: 'dist',
server: {
url: 'http://192.168.1.42:5173', // your host LAN IP and dev port
cleartext: true, // Android: allow plain HTTP to this URL
},
};
export default config;
The cleartext flag covers Android's cleartext policy at the Capacitor level. iOS is handled separately, below.
How you keep this override out of version control depends on your tooling. Capacitor's config loading has changed across major versions, so confirm the supported mechanism for your installed version before relying on it. A simple, always-correct alternative is to edit the file locally and revert it before committing.
Step 3 — Platform permissions
Android
Confirm android/app/src/main/AndroidManifest.xml permits cleartext traffic inside the <application> element:
<application
android:usesCleartextTraffic="true"
... >
On newer Android API levels this attribute alone may not be enough. If the WebView still refuses the connection, add a network security config limited to your host address:
<!-- android/app/src/main/res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="false">192.168.1.42</domain>
</domain-config>
</network-security-config>
Reference it from the same <application> element with android:networkSecurityConfig="@xml/network_security_config". Test on the API level you actually ship against; cleartext behavior has tightened over successive Android releases.
iOS
App Transport Security (ATS) blocks plain HTTP by default. For debug builds, the usual workaround is adding NSAppTransportSecurity with NSAllowsArbitraryLoads set to YES in the app's Info.plist. ATS exception domains are keyed by hostname and do not reliably accept bare IP literals, which is why the blanket flag is commonly used instead.
This is a debug-only change. Remove it before any TestFlight or App Store submission — an unexplained arbitrary-loads exception is both a review problem and a real security weakening.
Step 4 — Sync and launch
npx cap sync ios
npx cap sync android
# or launch directly on a connected device
npx cap run android --target=<device_id>
npx cap run ios --target=<device_id>
Run these from the project root. cap sync copies web assets and updates native dependencies, so it needs write access to the ios/ and android/ directories. Building and running from Xcode or Android Studio works too — the config change is what matters, not the launcher.
Expected checks
- The device browser test from Step 1 already passed.
- With remote debugging attached (Safari Web Inspector for iOS,
chrome://inspectfor Android), the page origin is your LAN URL, notcapacitor://localhostorhttps://localhost. - The debugger's network panel shows a WebSocket to the dev server that stays open. Vite's HMR client connects under
/@vite/; other dev servers use different paths. - Edit a component, save, and the device UI updates without a native rebuild. Timing depends on your network; a second or two is normal, longer on congested Wi-Fi.
- Introduce a deliberate syntax error. If the reload channel is bidirectional, the device shows the dev server's error overlay. If it does not, the page loaded once but the HMR socket is not connected.
Console log wording varies between Capacitor and dev-server versions. Do not treat any specific log line as the pass criterion; the origin check and the live edit are the reliable signals.
Recovery when it fails
| Symptom | Likely cause | Action |
|---|---|---|
| Blank screen or connection refused | Server bound to loopback, or firewall blocking | Restart the dev server with --host; allow inbound TCP on the port in the host firewall |
| Loads in the device browser but not in the app | Cleartext blocked by the platform | Android: manifest attribute, then network security config. iOS: ATS exception |
| Page loads, edits do nothing | HMR WebSocket blocked | Check AP isolation; try a phone hotspot or USB tethering |
| Worked yesterday, not today | Host IP changed (DHCP) | Re-read the LAN IP, update server.url, then npx cap sync |
Rolling back to bundled assets
Live reload changes native state, so revert deliberately: remove the server block (or point url back at a local origin), run npx cap sync for each platform, and rebuild. Also remove the Android cleartext entries and the iOS ATS exception. Confirm the rollback by checking that the WebView origin is the local bundled origin again and that the app still runs with the dev server stopped.
Limitations worth knowing
- Anyone on the same network can reach your dev server, and front-end code delivered to the device includes whatever API keys it contains. Do not do this on untrusted networks.
- Some networks — guest Wi-Fi, corporate VLANs, VPNs with client isolation — will not pass device-to-host traffic at all. A phone hotspot or USB tethering is the usual fallback.
- Live reload does not exercise the same code path as bundled assets. Always do a final pass with the server block removed before shipping.
- These details reflect established Capacitor behavior and may drift with new major versions. Run
npx cap doctor, check the docs for your version, and treat anything version-specific here as needing confirmation on your own setup.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.