Die Integration von Next.js und MQTT verwirrt anfangs, weil Next.js Ihren Code an mehreren Orten ausführt: im Browser, auf einem Node.js-Server, in Serverless Functions und manchmal an der Edge. In diesem Leitfaden erfahren Sie, welche dieser Orte eine MQTT-Verbindung halten können, und Sie bauen die drei Muster, die fast jedes Projekt abdecken: eine Client Component, die über sicheren WebSocket abonniert, einen Route Handler, der mit serverseitigen Zugangsdaten publiziert, und eine Bridge über Server-Sent Events. Alle Beispiele nutzen den WebSocket-Endpunkt von ConnectMQ auf broker.connectmq.com sowie MQTT.js v5 mit dem App Router.
Wo kann MQTT in Next.js laufen?
MQTT braucht eine langlebige Verbindung. Diese eine Tatsache entscheidet, wo es hinpasst:
| Laufzeitumgebung | Langlebige MQTT-Verbindung? | Transport | Typische Nutzung |
|---|---|---|---|
| Browser (Client Component) | Ja, solange der Tab geöffnet ist | wss:// Port 8084, Pfad /mqtt |
Live-Dashboards, Gerätesteuerung |
Node.js-Server (next start, Container, VM) |
Ja | mqtts:// 8883 oder wss:// 8084 |
Gemeinsamer Subscriber, SSE-Bridge, Publizieren |
| Serverless Function | Nein, nur pro Request | mqtts:// 8883 |
Einen Befehl publizieren, dann trennen |
| Edge Runtime | Nein | Für MQTT.js über TCP nicht unterstützt | Für MQTT meiden |
| Server Components | Nein, sie rendern einmal | - | Daten stattdessen aus einer Datenbank lesen |
Eine Server Component rendert und ist dann fertig; sie kann keine Subscription aufrechterhalten. Auch Server Actions sind an den Request gebunden. Die eigentliche Wahl besteht also zwischen einer Verbindung aus dem Browser und einer Verbindung aus einer Server-Route, die auf Node.js läuft.
Installieren Sie die Client-Bibliothek einmal:
npm install mqtt
MQTT.js v5 liefert sowohl einen Node.js-Build als auch einen Browser-Build, und Bundler wählen automatisch den richtigen. Der Leitfaden zu MQTT.js in Node.js behandelt die Bibliothek selbst; hier konzentrieren wir uns darauf, wie sie in Next.js passt.
Muster 1: In einer Client Component über WebSocket abonnieren
Für Live-Dashboards verbindet sich der Browser direkt mit dem Broker. Es gibt keinen Umweg über den Server, die Latenz ist minimal, und Ihr Next.js-Server leistet pro Nachricht keine Arbeit. Browser können kein rohes TCP öffnen, daher verwendet dieses Muster immer MQTT über sicheren WebSocket. Unser Artikel zu MQTT over WebSocket erklärt den Transport.
Erstellen Sie einen wiederverwendbaren Hook in app/hooks/useMqtt.js:
"use client";
import { useEffect, useRef, useState } from "react";
import mqtt from "mqtt";
const BROKER_URL = process.env.NEXT_PUBLIC_MQTT_URL ?? "wss://broker.connectmq.com:8084/mqtt";
export function useMqtt(topic) {
const clientRef = useRef(null);
const [status, setStatus] = useState("connecting");
const [messages, setMessages] = useState([]);
useEffect(() => {
const clientId = "next-" + Math.random().toString(16).slice(2, 10);
const client = mqtt.connect(BROKER_URL, {
clientId,
protocolVersion: 5,
clean: true,
keepalive: 30,
reconnectPeriod: 3000,
});
clientRef.current = client;
client.on("connect", () => {
setStatus("connected");
client.subscribe(topic, { qos: 1 });
});
client.on("reconnect", () => setStatus("reconnecting"));
client.on("close", () => setStatus("disconnected"));
client.on("message", (t, payload) => {
setMessages((prev) => [...prev.slice(-99), { topic: t, text: payload.toString(), at: Date.now() }]);
});
return () => {
client.end(true);
clientRef.current = null;
};
}, [topic]);
const publish = (t, payload) => clientRef.current?.publish(t, payload, { qos: 1 });
return { status, messages, publish };
}
Verwenden Sie ihn in einer Seite, app/live/page.jsx:
"use client";
import { useMqtt } from "../hooks/useMqtt";
const PREFIX = "connectmq/demo/next-k7f3q2";
export default function LivePage() {
const { status, messages } = useMqtt(`${PREFIX}/sensors/#`);
return (
<main style={{ padding: 24 }}>
<h1>Live sensors</h1>
<p>Broker: {status}</p>
<ul>
{messages.map((m) => (
<li key={m.at + m.topic}>{m.topic}: {m.text}</li>
))}
</ul>
</main>
);
}
React escapet Text in JSX, daher ist es sicher vor Script-Injection, Payloads auf diese Weise zu rendern. Publizieren Sie einen Testwert aus einem Terminal und sehen Sie zu, wie er erscheint:
mosquitto_pub -h broker.connectmq.com -p 1883 -i next-test-3b1c -t 'connectmq/demo/next-k7f3q2/sensors/room1' -m '{"t":22.4}'
Drei Details, die in der Entwicklung zuschlagen
- Doppeltes Mounten in Strict Mode. In der Entwicklung mountet React Komponenten, unmountet sie und mountet sie erneut, um Cleanup-Fehler sichtbar zu machen. Ohne
client.end()im Cleanup des Effects erhalten Sie zwei Verbindungen. Die Client ID innerhalb des Effects zu erzeugen verhindert außerdem, dass beide Instanzen um dieselbe ID kämpfen. - Nie auf Modulebene verbinden. Ein
mqtt.connect()auf oberster Ebene einer Client-Datei läuft auch beim serverseitigen Rendern dieser Komponente und öffnet dann eine TCP-Verbindung auf Ihrem Server. Halten Sie Verbindungen inuseEffect, das nur im Browser läuft. NEXT_PUBLIC_heißt öffentlich. Jede Variable mit diesem Präfix wird in das JavaScript-Bundle eingebettet. Legen Sie dort nur die Broker-URL ab, niemals ein Passwort.
Muster 2: Aus einem Route Handler mit privaten Zugangsdaten publizieren
Befehle, die die physische Welt verändern – etwa ein Ventil öffnen oder ein Gerät neu starten –, sollten nicht mit Zugangsdaten gesendet werden, die jeder Besucher lesen kann. Stattdessen ruft der Browser Ihre API auf, der Server prüft den Nutzer, und der Server publiziert mit Zugangsdaten, die in Umgebungsvariablen liegen.
Zuerst ein serverseitiges Singleton in lib/mqtt-server.js. Das Cachen des Clients auf globalThis verhindert, dass Hot Reload in der Entwicklung bei jedem Speichern einer Datei eine neue Verbindung erzeugt:
import mqtt from "mqtt";
const globalForMqtt = globalThis;
export function getMqttClient() {
if (!globalForMqtt.__mqttClient) {
globalForMqtt.__mqttClient = mqtt.connect(process.env.MQTT_URL ?? "mqtts://broker.connectmq.com:8883", {
clientId: process.env.MQTT_CLIENT_ID ?? "next-server-" + Math.random().toString(16).slice(2, 10),
username: process.env.MQTT_USERNAME,
password: process.env.MQTT_PASSWORD,
protocolVersion: 5,
clean: true,
reconnectPeriod: 5000,
});
}
return globalForMqtt.__mqttClient;
}
Dann der Route Handler in app/api/command/route.js:
import { getMqttClient } from "@/lib/mqtt-server";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
const PREFIX = "connectmq/demo/next-k7f3q2";
const ALLOWED = new Set(["on", "off"]);
export async function POST(request) {
const { deviceId, action } = await request.json();
if (!/^[a-z0-9-]{1,32}$/.test(deviceId ?? "") || !ALLOWED.has(action)) {
return Response.json({ error: "invalid command" }, { status: 400 });
}
// Hier vor dem Publizieren Session oder Rolle des Nutzers prüfen.
await getMqttClient().publishAsync(
`${PREFIX}/devices/${deviceId}/cmd`,
JSON.stringify({ action, at: new Date().toISOString() }),
{ qos: 1 }
);
return Response.json({ ok: true });
}
Ihre .env.local für einen privaten ConnectMQ-Broker sieht so aus:
MQTT_URL=mqtts://YOUR_BROKER_HOST:8883
MQTT_USERNAME=YOUR_USERNAME
MQTT_PASSWORD=YOUR_PASSWORD
MQTT_CLIENT_ID=YOUR_CLIENT_ID
deviceId gegen ein strenges Muster zu validieren ist nicht optional. Lassen Sie Nutzereingaben direkt in ein Topic fließen, könnte ein Request mit +, # oder zusätzlichen Schrägstrichen auf Topics publizieren, die Sie nie vorgesehen hatten.
Auf Serverless-Plattformen
Beim Serverless-Hosting kann eine Funktionsinstanz zwischen Requests eingefroren oder verworfen werden, sodass Sie sich auf keine gecachte Verbindung verlassen können. Verbinden, publizieren und schließen Sie stattdessen im selben Request:
import mqtt from "mqtt";
export const runtime = "nodejs";
export async function POST(request) {
const { message } = await request.json();
const client = await mqtt.connectAsync(process.env.MQTT_URL, {
clientId: "next-fn-" + Math.random().toString(16).slice(2, 10),
username: process.env.MQTT_USERNAME,
password: process.env.MQTT_PASSWORD,
});
try {
await client.publishAsync("connectmq/demo/next-k7f3q2/notices", String(message), { qos: 1 });
} finally {
await client.endAsync();
}
return Response.json({ ok: true });
}
Das fügt jedem Request einen TLS-Handshake hinzu – in Ordnung für gelegentliche Befehle, aber nicht für hochfrequentes Publizieren. Beachten Sie, dass ConnectMQ auf privaten Brokern Client IDs an die ausgegebenen Zugangsdaten bindet. Wenn Sie viele kurzlebige Funktionen gleichzeitig betreiben, prüfen Sie im Portal, wie Ihre Zugangsdaten und Client IDs bereitgestellt werden.
Muster 3: MQTT per Server-Sent Events an den Browser streamen
Manchmal soll der Browser gar nicht mit dem Broker sprechen, zum Beispiel wenn die Topic-Daten pro Nutzer gefiltert werden müssen. Eine Node.js-Route kann eine Subscription halten und die Nachrichten per Server-Sent Events (SSE) an Browser verteilen. Das funktioniert nur auf einem dauerhaft laufenden Node.js-Server, nicht in Serverless Functions mit kurzen Timeouts.
Abonnieren Sie einmal im Singleton (fügen Sie dies in getMqttClient nach dem Erzeugen des Clients ein):
globalForMqtt.__mqttClient.on("connect", () => {
globalForMqtt.__mqttClient.subscribe("connectmq/demo/next-k7f3q2/sensors/#", { qos: 1 });
});
Ergänzen Sie dann app/api/stream/route.js:
import { getMqttClient } from "@/lib/mqtt-server";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
export async function GET(request) {
const client = getMqttClient();
const encoder = new TextEncoder();
const stream = new ReadableStream({
start(controller) {
const onMessage = (topic, payload) => {
const data = JSON.stringify({ topic, payload: payload.toString() });
controller.enqueue(encoder.encode(`data: ${data}\n\n`));
};
client.on("message", onMessage);
request.signal.addEventListener("abort", () => {
client.off("message", onMessage);
controller.close();
});
},
});
return new Response(stream, {
headers: { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive" },
});
}
In einer Client Component empfängt new EventSource("/api/stream") jede Nachricht als Event. Eine einzige Broker-Verbindung bedient nun alle Betrachter, was Sie bequem innerhalb der Verbindungslimits hält.
Fehlerbehebung bei Next.js-MQTT-Verbindungen
Einige Probleme treten beim Einbinden von MQTT in ein Next.js-Projekt immer wieder auf:
- „WebSocket connection failed" in der Browserkonsole. Prüfen Sie die URL genau: Sie muss
wss://und Port 8084 verwenden und auf/mqttenden. Eine über HTTPS ausgelieferte Seite kann kein unverschlüsseltesws://nutzen. - „Module not found: net" oder „tls" beim Build. Etwas hat reinen Server-Code in ein Client-Bundle gezogen, meist über eine gemeinsame Datei, die auch Node.js-Module importiert. Halten Sie
lib/mqtt-server.jsaus allem heraus, was mit"use client"markiert ist. - Der Route Handler hängt und läuft dann in einen Timeout. Der Broker hat den Login abgelehnt oder der Port ist von Ihrem Host aus blockiert, sodass
publishAsyncauf eine Verbindung wartet, die nie zustande kommt. Hören Sie auf daserror-Event, protokollieren Sie es und testen Sie dieselben Zugangsdaten mitmosquitto_pubvom Server aus. - Nachrichten kommen auf dem Server doppelt an. Hot Reload hat einen zweiten Client erzeugt, weil das Singleton nicht auf
globalThisgecacht war. Starten Sie den Dev-Server nach der Korrektur neu.
Das richtige Muster wählen
Entscheiden Sie anhand dieser Checkliste:
- Nur lesende Live-Daten, öffentlich oder wenig sensibel: Muster 1, Browser über WebSocket mit Nur-Lese-Zugangsdaten.
- Aktionen mit Seiteneffekten: Muster 2, Route Handler mit serverseitigen Zugangsdaten und Eingabevalidierung.
- Filterung pro Nutzer oder viele Betrachter bei einem kleinen Plan: Muster 3, SSE-Bridge auf einem Node.js-Server.
- Historische Diagramme: keines der obigen allein. Speichern Sie Nachrichten mit einem separaten Subscriber und fragen Sie die Datenbank aus einer Server Component ab.
Die meisten Produktions-Apps kombinieren 1 und 2: Der Browser abonniert Live-Updates, und jeder Befehl läuft über einen Route Handler. In Industrie-4.0-Projekten ist das typische Bild ein Shopfloor-Dashboard, das einen Zweig des Unified Namespace (UNS) wie acme/werk-muenchen/montage/# abonniert, während Befehle an Maschinen mit Autorisierung und Audit-Logging über den Route Handler laufen. Details auf Komponentenebene finden Sie im React-MQTT-Tutorial, und für typisierte Payloads lesen Sie den Leitfaden zu MQTT mit TypeScript. Das Tutorial zur MQTT-Chat-App ist ein gutes Beispiel für ein rein browserseitiges Design, und die Übersicht über MQTT-Client-Bibliotheken hilft, wenn ein Teil Ihres Stacks in einer anderen Sprache geschrieben ist. Die offizielle Next.js-Dokumentation zu Route Handlern behandelt die Laufzeitoptionen ausführlich.
Verbinden Sie Ihre Next.js-App mit ConnectMQ
ConnectMQ ist ein verwalteter MQTT-Broker mit sicherem WebSocket auf 8084 für Client Components und TLS auf 8883 für Route Handler, sodass beide Seiten einer Next.js-App mit demselben Broker sprechen. Entwickeln Sie Prototypen gegen den öffentlichen MQTT-Broker, der nicht für Produktionsdaten gedacht ist, und wechseln Sie dann auf einen privaten MQTT-Broker in der Cloud. Registrieren Sie sich kostenlos im Serverless-Plan, ohne Kreditkarte.
Häufig gestellte Fragen
Kann ich MQTT in Next.js Server Components verwenden?
Nicht für Live-Daten. Server Components rendern einmal pro Request und können keine Subscription halten. Abonnieren Sie in einer Client Component oder einem Node.js-Route-Handler und nutzen Sie Server Components, um gespeicherte Daten zu lesen.
Funktioniert MQTT in der Edge Runtime von Next.js?
Die Edge Runtime stellt nicht die Node.js-Netzwerk-APIs bereit, die MQTT.js für TCP und TLS verwendet. Setzen Sie export const runtime = "nodejs" in jeder Route, die mit dem Broker spricht.
Wie halte ich MQTT-Zugangsdaten in Next.js vom Browser fern?
Speichern Sie sie in nicht öffentlichen Umgebungsvariablen und verwenden Sie sie nur in Route Handlern oder Server-Code. Alles mit dem Präfix NEXT_PUBLIC_ landet im Client-Bundle.
Warum sehe ich in der Entwicklung zwei MQTT-Verbindungen?
React Strict Mode mountet Komponenten in der Entwicklung zweimal, und das Hot Reload von Next.js führt Server-Module erneut aus. Schließen Sie den Client im Cleanup des Effects und cachen Sie Server-Clients auf globalThis.



