Um den Netzwerkstatus in einer Capacitor-App zu erkennen, installiere @capacitor/network, rufe Network.getStatus() für den aktuellen Status und abonnieren Sie networkStatusChange für Updates. Das zeigt Ihnen, ob das Gerät verbunden ist und ob es sich um Wi-Fi oder Mobilfunk handelt. Um zu prüfen, ob das Internet tatsächlich funktioniert oder ob die Verbindung getaktet ist, fügen Sie einen Reachability-Check oder @capgo/capacitor-network-diagnosticshinzu. Dieser Guide deckt alle drei Ebenen mit Capacitor 8 code ab.
Die drei Fragen hinter „Ist der User online?“
| Frage | API | Notizen |
|---|---|---|
| Gibt es eine aktive Netzwerkschnittstelle? | Network.getStatus().connected |
Schnell, eventgesteuert, sagt nichts über den Internetzugriff aus |
| Um welche Art von Verbindung handelt es sich? | connectionType |
wifi, cellular, none, unknown im offiziellen Plugin |
| Funktioniert das Internet tatsächlich? | HTTP-Check oder internetReachable |
Benötigt eine Anfrage oder OS-Validierung |
| Große Downloads vermeiden? | expensive, constrained |
Gebührenpflichtige Netzwerke und iOS Low Data Mode |
Die meisten Offline-Bugs entstehen, wenn man die erste Zeile wie die dritte behandelt. Ein Hotel-WLAN mit Login-Seite meldet connected: true während jeder API-Aufruf in einem Timeout endet.
Network-Plugin installieren
bun add @capacitor/network
bunx cap sync
Keine Konfiguration erforderlich. Auf Android fügt das Plugin ACCESS_NETWORK_STATE zum gemergten Manifest hinzu – eine normale Berechtigung, die bei der Installation erteilt wird. iOS benötigt nichts.
Aktuellen Status auslesen
import { Network } from '@capacitor/network';
const status = await Network.getStatus();
console.log(status.connected); // true or false
console.log(status.connectionType); // 'wifi' | 'cellular' | 'none' | 'unknown'
Rufe es beim App-Start auf, bevor du entscheidest, ob du aus dem Cache oder dem Netzwerk lädst. Es liefert in wenigen Millisekunden ein Ergebnis, da es den OS-Zustand liest und keinen Traffic sendet.
Auf Netzwerkänderungen lauschen
import { Network, type ConnectionStatus } from '@capacitor/network';
import type { PluginListenerHandle } from '@capacitor/core';
let handle: PluginListenerHandle | undefined;
export async function watchNetwork(onChange: (s: ConnectionStatus) => void) {
onChange(await Network.getStatus());
handle = await Network.addListener('networkStatusChange', onChange);
}
export async function stopWatchingNetwork() {
await handle?.remove();
}
Zwei praktische Details:
- Das Event debouncen. Beim Wechsel von Wi-Fi zu Mobilfunk können beide Plattformen kurzzeitig melden,
nonebevor die neue Verbindung steht. Ein Debounce von 1 bis 2 Sekunden, bevor ein „Offline“-Banner erscheint, verhindert Flackern. - Listener entfernen wenn eine Komponente unmountet wird, sonst läuft der Callback nach Hot Reloads und Navigation mehrfach.
React Hook
import { useEffect, useState } from 'react';
import { Network, type ConnectionStatus } from '@capacitor/network';
export function useNetworkStatus() {
const [status, setStatus] = useState<ConnectionStatus>({ connected: true, connectionType: 'unknown' });
useEffect(() => {
let timer: ReturnType<typeof setTimeout>;
const listener = Network.addListener('networkStatusChange', (s) => {
clearTimeout(timer);
timer = setTimeout(() => setStatus(s), s.connected ? 0 : 1500);
});
Network.getStatus().then(setStatus);
return () => {
clearTimeout(timer);
listener.then((l) => l.remove());
};
}, []);
return status;
}
Der Online-Status wird sofort angewendet, der Offline-Status wartet 1,5 Sekunden. Das gleiche Pattern funktioniert als Vue Composable oder Angular Service mit einem BehaviorSubject.
Warum „connected“ nicht „online“ bedeutet
connected: true kommt aus dem Netzwerk-Stack des OS. Er bleibt true, wenn:
- das Wi-Fi-Netz ein Captive Portal hat (Flughäfen, Hotels, Züge)
- der Router keine Upstream-Verbindung hat
- DNS ist gestört oder gefiltert
- eine Firmen-Firewall oder ein VPN blockiert deine API Domain
- dein eigenes Backend ist offline
Die Lösung ist ein Reachability-Check gegen etwas, das du kontrollierst:
export async function canReachApi(timeoutMs = 4000): Promise<boolean> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch('https://api.example.com/health', {
method: 'HEAD',
cache: 'no-store',
signal: controller.signal,
});
return res.ok;
} catch {
return false;
} finally {
clearTimeout(timer);
}
}
Führe ihn beim App-Start aus, wenn networkStatusChange meldet connected: true, und nachdem ein Request fehlschlägt. Nutze kein Polling alle paar Sekunden, das verbraucht Akku und Daten. Stelle sicher, dass der Health-Endpoint den Origin deiner App in CORS erlaubt (capacitor://localhost unter iOS, https://localhost unter Android standardmäßig), oder nutze CapacitorHttp um es über natives code zu senden.
Erhalte detailliertere Statusinfos mit Netzwerk-Diagnose
@capgo/capacitor-network-diagnostics liest mehr aus dem OS aus und kann native Checks ausführen, die WebView CORS umgehen:
bun add @capgo/capacitor-network-diagnostics
bunx cap sync
import { NetworkDiagnostics } from '@capgo/capacitor-network-diagnostics';
const status = await NetworkDiagnostics.getNetworkStatus();
// {
// connected: true,
// connectionType: 'wifi' | 'cellular' | 'ethernet' | 'vpn' | 'other' | 'unknown' | 'none',
// internetReachable: true,
// expensive: false, // metered / cellular
// constrained: false, // iOS Low Data Mode
// captivePortal: false, // Android only
// details: { ... }
// }
So werden die Felder auf das OS abgebildet:
| Feld | Android | iOS |
|---|---|---|
internetReachable |
NET_CAPABILITY_VALIDATED (Android hat das Internet geprüft) |
Spiegelt den NWPathMonitor Pfadstatus |
expensive |
isActiveNetworkMetered() |
NWPath.isExpensive |
constrained |
Immer false |
NWPath.isConstrained (Modus mit geringem Datenvolumen) |
captivePortal |
NET_CAPABILITY_CAPTIVE_PORTAL |
Keine Angabe |
vpn type |
Ja | Ja |
Unter Android, internetReachable ist ein starkes Signal, da das OS die Verbindung bereits getestet hat. Auf iOS ist es näher an connected, behalte also den HTTP-Check für kritische Flows bei.
Native Erreichbarkeitsprüfungen
const api = await NetworkDiagnostics.testUrl({
url: 'https://api.example.com/health',
method: 'HEAD',
timeoutMs: 4000,
});
// api.reachable, api.statusCode, api.durationMs, api.errorCode
const socket = await NetworkDiagnostics.testWebSocket({ url: 'wss://realtime.example.com' });
const port = await NetworkDiagnostics.testPort({ host: 'mqtt.example.com', port: 8883 });
Native Requests unterliegen nicht CORS, sodass du auch Drittanbieter-Hosts testen kannst.
Ein Support-„Netzwerkbericht“-Screen
Wenn Nutzer sagen „Die App funktioniert nicht“, spart ein Diagnosebericht mit einem Tap Stunden an Hin- und Hergerede:
const report = await NetworkDiagnostics.runDiagnostics({
urls: [{ url: 'https://api.example.com/health' }, { url: 'https://cdn.example.com/ping' }],
websockets: [{ url: 'wss://realtime.example.com' }],
download: { url: 'https://cdn.example.com/1mb.bin', maxBytes: 1_048_576 },
packetLoss: { url: 'https://api.example.com/health', count: 5 },
});
console.log(report.issues); // human-readable list of problems
console.log(report.download?.mbps);
console.log(report.packetLoss?.lossPercent);
Häng das JSON an ein Support-Ticket an. Du siehst sofort, ob das Problem DNS ist, ein blockierter WebSocket, ein Captive Portal oder eine langsame Verbindung.
Anpassung an gebührenpflichtige Verbindungen und Low Data Mode
Datengrenzen zu respektieren ist ein einfacher Hebel für bessere Bewertungen in Märkten mit teuren mobilen Daten:
export async function shouldPrefetchMedia() {
const s = await NetworkDiagnostics.getNetworkStatus();
if (!s.connected) return false;
if (s.constrained) return false; // user asked iOS to save data
if (s.expensive) return false; // cellular or metered Wi-Fi
return true;
}
Nutze es, um über Video-Autoplay, Bildqualität, Hintergrund-Sync und große Downloads zu entscheiden. Auf Android können Nutzer ein Wi-Fi-Netzwerk als gebührenpflichtig markieren, geh also nicht davon aus, dass Wi-Fi kostenlos ist.
Offline-Banner erstellen
Ein kleines Banner ist klarer als fehlgeschlagene Requests. Mit dem Hook oben:
export function OfflineBanner() {
const { connected } = useNetworkStatus();
if (connected) return null;
return (
<div role="status" aria-live="polite" className="offline-banner">
You are offline. Changes will sync when you reconnect.
</div>
);
}
role="status" mit aria-live="polite" lässt Screenreader die Änderung ansagen. Für framework-spezifische UI siehe unseren Guide zum Bauen eines Offline-Screens in Vue, Angular und React.
Arbeit offline in die Warteschlange stellen
Erkennung ist nur die halbe Miete beim Offline-Support. Wenn der Status auf offline wechselt:
- Liest aus dem lokalen Speicher. Eine SQLite-Datenbank wie
@capgo/capacitor-fast-sqlkommt mit großen Datensätzen besser zurecht alslocalStorage. - Schreibe Nutzeraktionen in eine Outbox-Tabelle mit Zeitstempel und Idempotenzschlüssel.
- An
networkStatusChangeverbunden, führe auscanReachApi(), dann die Outbox der Reihe nach leeren. - Wiederhole fehlgeschlagene Anfragen mit exponentiellem Backoff, begrenzt auf wenige Minuten.
Für Uploads, die das Schließen der App überleben müssen, übergib sie an einen nativen Uploader statt an fetch.
Web-Fallback
@capacitor/network läuft auch im Browser und basiert dort auf navigator.onLine und online und offline events. Das bedeutet, dass dein code in bun run dev und in einer PWA ohne Änderungen, aber connectionType wird im Web oft unknown sein. Chrome’s navigator.connection stellt effectiveType und saveData, Safari tut dies nicht; behandle sie also als optionale Hinweise.
Fehlerbehebung
Der Listener wird im iOS Simulator nie ausgelöst: Der Simulator nutzt das Netzwerk des Macs. Schalte das WLAN am Mac um oder teste auf einem Gerät im Flugmodus.
connectionType ist unknown über ein VPN: das offizielle Plugin meldet nur wifi, cellular, none und unknown. Nutze das Diagnostics-Plugin, das zurückgibt vpn und ethernet.
Die App zeigt beim Netzwerkwechsel kurzzeitig „offline“ an: den Offline-Übergang wie oben gezeigt debouncen.
Der Health Check schlägt auf dem Gerät fehl, funktioniert aber im Browser: CORS. Erlauben capacitor://localhost und https://localhost, oder nutze NetworkDiagnostics.testUrl, das nativ läuft.
navigator.onLine ist true, während das Plugin offline meldet: vertrauen Sie dem Plugin. Einige Android WebViews aktualisieren nicht navigator.onLine zuverlässig.
Offline-Fixes via Over-the-Air ausrollen
Offline-Handling ist meist JavaScript: Thresholds, Banner, Retry-Logik, Cache-Regeln. Mit Capgo Live Updates, kannst du diese noch am selben Tag für jeden Nutzer anpassen. Capgo lädt Update-Bundles im Hintergrund herunter und behält die aktuelle Version, falls ein Download fehlschlägt. So bleibt niemand bei schlechter Verbindung mit einem halb installierten Update zurück. Die Netzwerkdiagnose-Docs listet jede Option auf.