MQTT im Browser mit JavaScript ist der schnellste Weg zu einem Live-Dashboard, einem Fernsteuerungs-Panel oder einem Debugging-Werkzeug – ganz ohne Backend, ob als Statusanzeige für eine Fertigungslinie auf dem Hallenmonitor oder als schnelles Prüfwerkzeug bei der Inbetriebnahme. In diesem Tutorial erstellen Sie eine eigenständige HTML-Seite, die sich über sicheren WebSocket mit broker.connectmq.com verbindet, ein Topic abonniert, Nachrichten publiziert und sie in Echtzeit anzeigt. Außerdem erfahren Sie, warum Browser für MQTT einen WebSocket brauchen, warum CORS kein Problem ist und wie Sie dieselbe Seite sicher auf einen privaten Broker umstellen.
Warum ein Browser MQTT over WebSocket braucht
Eine Webseite kann keine rohen TCP-Sockets öffnen. Die Sandbox des Browsers lässt JavaScript nur über HTTP(S), WebSocket, WebRTC und einige neuere APIs mit dem Netzwerk kommunizieren. Standard-MQTT läuft über TCP auf Port 1883 (bzw. TLS auf 8883), daher kann ein Browser diese Ports nicht direkt erreichen.
Die MQTT-Spezifikation löst das, indem sie erlaubt, MQTT-Pakete in binären WebSocket-Frames zu transportieren. Der Broker stellt einen zusätzlichen Listener bereit, der Browser öffnet einen WebSocket dorthin, und ab dann ist der Austausch ganz normales MQTT: CONNECT, SUBSCRIBE, PUBLISH, PINGREQ. Wenn Sie die Details auf Protokollebene interessieren, führt unser Leitfaden zu MQTT over WebSocket durch den Handshake und den Subprotocol-Header.
ConnectMQ stellt auf jedem Broker zwei WebSocket-Listener bereit:
| Endpunkt | URL | Wann verwenden |
|---|---|---|
| WebSocket | ws://broker.connectmq.com:8083/mqtt |
Nur für lokale Tests von einer http://-Seite |
| Sicherer WebSocket | wss://broker.connectmq.com:8084/mqtt |
Jede über https:// ausgelieferte Seite und jeder Produktionseinsatz |
Verwenden Sie standardmäßig wss://. Eine über HTTPS geladene Seite darf keine unverschlüsselte ws://-Verbindung öffnen; der Browser blockiert sie als Mixed Content, bevor auch nur ein Paket den Rechner verlässt.
Und was ist mit CORS?
CORS (Cross-Origin Resource Sharing) gilt für fetch und XMLHttpRequest. WebSocket-Verbindungen unterliegen keinen CORS-Preflight-Prüfungen. Der Browser sendet beim Upgrade einen Origin-Header, den ein Server prüfen kann, aber es gibt kein Hin und Her mit Access-Control-Allow-Origin. Deshalb können Sie die HTML-Datei unten vom Desktop, von einem lokalen Dev-Server oder von jeder beliebigen Domain öffnen, und sie verbindet sich ohne Proxy-Tricks.
Die Bibliothek wählen: MQTT.js
MQTT.js ist der De-facto-Standard-MQTT-Client für JavaScript. Dasselbe Paket läuft in Node.js und in Browsern, unterstützt MQTT 3.1.1 und MQTT 5 und übernimmt für Sie Reconnects, Keepalive sowie die Abläufe für QoS 1 und 2. Version 5 liefert ein vorgefertigtes Browser-Bundle mit, sodass Sie es mit einem einzigen Script-Tag von einem CDN laden können. Die MQTT.js-Dokumentation auf GitHub listet alle Optionen auf; die Teilmenge unten ist alles, was ein minimaler Client braucht.
Wechseln Sie später zu einem Bundler oder Framework, ändert sich der Code kaum. Unser Leitfaden zu MQTT.js mit Node.js behandelt die Serverseite, und das React-MQTT-Tutorial zeigt, wie Sie denselben Client in Hooks verpacken.
Der vollständige MQTT-JavaScript-Client für den Browser in einer Seite
Speichern Sie Folgendes als mqtt-client.html und öffnen Sie es in einem beliebigen modernen Browser. Die Seite lädt MQTT.js von einem CDN, erzeugt eine eindeutige Client ID und ein zufälliges Topic-Präfix und bietet Buttons zum Verbinden, Abonnieren und Publizieren.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Minimal MQTT Browser Client</title>
<script src="https://unpkg.com/mqtt@5.10.1/dist/mqtt.min.js"></script>
<style>
body { font-family: system-ui, sans-serif; max-width: 720px; margin: 2rem auto; padding: 0 1rem; }
input, button { font: inherit; padding: .4rem .6rem; }
#log { border: 1px solid #ccc; height: 280px; overflow-y: auto; padding: .5rem; font-family: monospace; font-size: .85rem; }
.row { display: flex; gap: .5rem; margin: .5rem 0; }
.row input { flex: 1; }
</style>
</head>
<body>
<h1>MQTT in the Browser</h1>
<p>Status: <strong id="status">disconnected</strong></p>
<div class="row">
<input id="url" value="wss://broker.connectmq.com:8084/mqtt">
<button id="connectBtn">Connect</button>
<button id="disconnectBtn">Disconnect</button>
</div>
<div class="row">
<input id="subTopic">
<button id="subBtn">Subscribe</button>
</div>
<div class="row">
<input id="pubTopic">
<input id="pubPayload" value='{"temperature": 21.5}'>
<button id="pubBtn">Publish</button>
</div>
<div id="log"></div>
<script>
const randomId = Math.random().toString(16).slice(2, 10);
const prefix = `connectmq/demo/${randomId}`;
document.getElementById("subTopic").value = `${prefix}/#`;
document.getElementById("pubTopic").value = `${prefix}/sensors/room1`;
const statusEl = document.getElementById("status");
const logEl = document.getElementById("log");
let client = null;
function log(text) {
const line = document.createElement("div");
line.textContent = `[${new Date().toLocaleTimeString()}] ${text}`;
logEl.appendChild(line);
logEl.scrollTop = logEl.scrollHeight;
}
document.getElementById("connectBtn").onclick = () => {
if (client) return;
const url = document.getElementById("url").value;
client = mqtt.connect(url, {
clientId: `browser-${randomId}`,
protocolVersion: 5,
clean: true,
keepalive: 30,
connectTimeout: 10000,
reconnectPeriod: 3000
// Für einen privaten ConnectMQ-Broker ergänzen:
// username: "YOUR_USERNAME",
// password: "YOUR_PASSWORD"
});
client.on("connect", () => { statusEl.textContent = "connected"; log(`Connected to ${url}`); });
client.on("reconnect", () => { statusEl.textContent = "reconnecting"; });
client.on("close", () => { statusEl.textContent = "disconnected"; });
client.on("offline", () => log("Client went offline"));
client.on("error", (err) => log(`Error: ${err.message}`));
client.on("message", (topic, payload, packet) => {
const retained = packet.retain ? " (retained)" : "";
log(`${topic}${retained}: ${payload.toString()}`);
});
};
document.getElementById("disconnectBtn").onclick = () => {
if (!client) return;
client.end(false, () => log("Disconnected cleanly"));
client = null;
};
document.getElementById("subBtn").onclick = () => {
if (!client) return log("Connect first");
const topic = document.getElementById("subTopic").value;
client.subscribe(topic, { qos: 1 }, (err, granted) => {
if (err) return log(`Subscribe failed: ${err.message}`);
granted.forEach(g => log(`Subscribed to ${g.topic} (granted QoS ${g.qos})`));
});
};
document.getElementById("pubBtn").onclick = () => {
if (!client) return log("Connect first");
const topic = document.getElementById("pubTopic").value;
const payload = document.getElementById("pubPayload").value;
client.publish(topic, payload, { qos: 1 }, (err) => {
if (err) log(`Publish failed: ${err.message}`);
});
};
</script>
</body>
</html>
Klicken Sie auf Connect, dann auf Subscribe und dann auf Publish. Die Nachricht wandert von Ihrer Seite zum Broker und direkt zurück zur selben Seite, weil Sie prefix/# abonniert haben, was auf das Publish-Topic passt.
Wie der Code funktioniert
Verbinden
mqtt.connect() gibt sofort einen Client zurück und verbindet sich im Hintergrund. Die Events verraten Ihnen, was als Nächstes passiert: connect nach einem erfolgreichen CONNACK, reconnect bei jedem erneuten Versuch, close, wenn der Socket geschlossen wird, und error bei Problemen wie einem abgelehnten Login. Mit protocolVersion: 5 erhalten Sie MQTT-5-Features wie Reason Codes und User Properties; entfernen Sie die Option, um auf 3.1.1 zurückzufallen.
Die Client ID wird bei jedem Laden der Seite neu erzeugt. Der öffentliche MQTT-Broker akzeptiert jede nicht leere Client ID, aber zwei Clients mit derselben ID werfen sich gegenseitig in einer Schleife hinaus, was wie eine flatternde Verbindung aussieht. Zufällige IDs verhindern das, wenn Sie die Seite in zwei Tabs öffnen.
Abonnieren und empfangen
Das message-Event feuert bei jedem eingehenden PUBLISH. payload ist ein Byte-Puffer, rufen Sie also toString() für Text oder JSON.parse(payload.toString()) für JSON auf. Das dritte Argument ist das rohe Paket, das retain, qos und unter MQTT 5 auch properties bereitstellt.
Beachten Sie die Verwendung von textContent in der Funktion log. Jeder kann auf dem öffentlichen MQTT-Broker publizieren, behandeln Sie also jeden Payload als nicht vertrauenswürdige Eingabe. Ein Einfügen per innerHTML würde einer bösartigen Nachricht erlauben, Skripte in Ihrer Seite auszuführen.
Publizieren
client.publish(topic, payload, options, callback) stellt die Nachricht in die Warteschlange. Bei QoS 1 läuft der Callback, nachdem der Broker sie mit PUBACK bestätigt hat. Ist die Verbindung unterbrochen, hält MQTT.js QoS-1- und QoS-2-Nachrichten in seinem Ausgangsspeicher und sendet sie nach dem Reconnect, solange die Seite geöffnet bleibt. Der Leitfaden zu QoS-Stufen erklärt, wann diese Garantie wichtig ist.
Von der anderen Seite testen
Um zu beweisen, dass die Nachrichten wirklich über den Broker laufen, beobachten Sie sie in einem Terminal, während Sie auf der Seite auf Publish klicken. Ersetzen Sie das Präfix durch das, das Ihre Seite anzeigt:
mosquitto_sub -h broker.connectmq.com -p 1883 -i cli-watch-7f3a -t 'connectmq/demo/YOUR_PREFIX/#' -v
Publizieren Sie aus dem Terminal, und das Log im Browser aktualisiert sich sofort:
mosquitto_pub -h broker.connectmq.com -p 1883 -i cli-pub-7f3a -t 'connectmq/demo/YOUR_PREFIX/sensors/room1' -m '{"temperature": 23.1}'
Dieser Roundtrip vom Browser zum TCP-Client und zurück zeigt am deutlichsten, dass sich ein WebSocket-Client und ein nativer TCP-Client auf dem Broker denselben Topic-Raum teilen.
Checkliste zur Fehlerbehebung
Wenn der Status nie „connected" erreicht, gehen Sie diese Liste der Reihe nach durch:
- Falscher Pfad. ConnectMQ bedient WebSocket unter
/mqtt. Eine URL ohne Pfad scheitert beim HTTP-Upgrade. - Mixed Content. Eine
https://-Seite kann keinws://öffnen. Verwenden Siewss://auf Port 8084. - Port blockiert. Manche Unternehmensnetzwerke erlauben nur 443. Testen Sie von einem anderen Netzwerk oder einem Handy-Hotspot aus, um das auszuschließen.
- Doppelte Client ID. Zeigt das Log, dass sich Connect und Close alle paar Sekunden wiederholen, verwendet ein anderer Client dieselbe ID.
- Leere oder ungültige Zugangsdaten. Auf einem privaten Broker erzeugen ein falscher Benutzername oder ein falsches Passwort ein
error-Event mit „Not authorized" oder „Bad username or password". Prüfen Sie die Werte im Portal. - Limits des öffentlichen MQTT-Brokers. Der öffentliche MQTT-Broker erlaubt 10 Nachrichten pro Sekunde und Client, 10 Subscriptions pro Client, 64 KB pro Nachricht und 8 Topic-Ebenen. Ein Button, der an eine schnelle Schleife gekoppelt ist, stößt an das Ratenlimit.
- DevTools. Öffnen Sie den Network-Tab, filtern Sie nach WS und sehen Sie sich die Frames an. Sie sehen die binären MQTT-Pakete und den Statuscode der Upgrade-Antwort.
Vom öffentlichen MQTT-Broker zu einem privaten Broker wechseln
Der öffentliche MQTT-Broker ist für dieses Tutorial und andere Tests und Prototypen perfekt, aber jedes Topic darauf ist für jeden lesbar, der es errät. Produktionsdaten gehören dort nicht hin. Für echte Projekte erstellen Sie Ihren eigenen Broker:
- Registrieren Sie sich unter app.connectmq.com und wählen Sie den kostenlosen Serverless-Plan.
- Erstellen Sie einen Broker und kopieren Sie den generierten Benutzernamen, das Passwort und die Client ID.
- Ändern Sie die URL auf den sicheren WebSocket-Endpunkt Ihres Brokers auf Port 8084 mit dem Pfad
/mqttund setzen Sieusername,passwordundclientIdin den Optionen.
Achten Sie darauf, wo diese Zugangsdaten liegen. Alles, was Sie in Browser-JavaScript ablegen, kann jeder lesen, der die DevTools öffnet. Bewährte Muster sind:
- Verwenden Sie für Browser-Dashboards separate Zugangsdaten mit minimalen Rechten, niemals die Ihrer Geräte.
- Lassen Sie Nutzer die Zugangsdaten zur Laufzeit eingeben, statt sie in der Seite fest zu hinterlegen.
- Lassen Sie bei öffentlich zugänglichen Websites ein Backend das privilegierte Publizieren übernehmen und den Browser nur lesen. Der Artikel zu Next.js-MQTT-Mustern zeigt diese Aufteilung.
Der Leitfaden zu MQTT-Sicherheit: Best Practices geht tiefer auf den Umgang mit Zugangsdaten und das Topic-Design ein.
Wie es weitergeht
Diese einzelne Seite ist ein Fundament, das Sie in mehrere Richtungen ausbauen können:
- Live-Diagramme. Parsen Sie JSON-Payloads und übergeben Sie die Werte an eine Diagrammbibliothek. Verwenden Sie ein Array fester Länge, damit der Speicherverbrauch auch bei langen Sitzungen konstant bleibt.
- Chat und Präsenz. Retained Messages und Last Will machen es leicht anzuzeigen, wer online ist. Das Tutorial zur MQTT-Chat-App baut genau das.
- Typisierter Code. Wächst die Seite, wechseln Sie zu TypeScript und einem Bundler; der Leitfaden zu MQTT mit TypeScript behandelt die Typisierung von Payloads und Events.
- Andere Sprachen. Die Übersicht über MQTT-Client-Bibliotheken vergleicht MQTT.js mit Clients für Python, Go, Java und C#, falls Ihr Backend einen anderen Stack nutzt.
Starten Sie mit ConnectMQ
ConnectMQ bietet Ihnen einen verwalteten MQTT-Broker mit bereits konfiguriertem sicherem WebSocket auf Port 8084, einem Let's-Encrypt-Zertifikat sowie einem MQTT-Client im Browser und einem Live-Topic-Explorer im Portal, mit denen Sie prüfen, was Ihre Seite publiziert. Beginnen Sie noch heute mit dem öffentlichen MQTT-Broker und erstellen Sie einen privaten Cloud-MQTT-Broker, sobald Sie bereit sind. Erstellen Sie ein kostenloses Konto im Serverless-Plan, ohne Kreditkarte.
Häufig gestellte Fragen
Kann sich ein Browser über Port 1883 mit MQTT verbinden?
Nein. Browser können keine rohen TCP-Verbindungen öffnen und müssen daher MQTT over WebSocket verwenden. Bei ConnectMQ ist das Port 8083 für ws:// und Port 8084 für wss://, beide unter dem Pfad /mqtt.
Brauche ich ein Backend oder einen Proxy, um MQTT im Browser zu nutzen?
Nein. Die Seite verbindet sich über WebSocket direkt mit dem Broker, und WebSocket unterliegt keinem CORS-Preflight. Ein Backend ist nur dann sinnvoll, wenn Sie privilegierte Zugangsdaten vom Client fernhalten wollen.
Ist MQTT.js die einzige JavaScript-MQTT-Bibliothek für Browser?
Es ist die am weitesten verbreitete und aktiv gepflegte Option, mit MQTT-5-Unterstützung und einem fertigen Browser-Bundle. Der ältere Paho-JavaScript-Client funktioniert weiterhin, ist für neue Projekte aber nicht mehr die übliche Wahl.
Warum verbindet sich mein Browser-Client alle paar Sekunden neu?
Die häufigste Ursache sind zwei Clients mit derselben Client ID, wodurch der Broker jedes Mal den älteren trennt. Erzeugen Sie pro Tab eine zufällige ID und prüfen Sie außerdem, ob Sie die Ratenlimits des öffentlichen MQTT-Brokers überschreiten.



