IoT Eğitimleri

Next.js ile MQTT: Gerçekten İşe Yarayan Sunucu ve İstemci Entegrasyon Desenleri

Next.js App Router projesinde MQTT kullanmanın üç yolunu öğrenin: güvenli WebSocket üzerinden client component'ler, özel kimlik bilgileriyle publish yapan sunucu route handler'ları ve bir Server-Sent Events köprüsü.

Client component'ler, route handler'lar ve bulut MQTT broker ile next.js mqtt entegrasyonunu gösteren diyagram
Bunu kendi MQTT broker’ınızda çalıştırınFree planla özel, yönetilen bir MQTT broker alın ve hazır kod örnekleriyle başlayın.
Ücretsiz deneyin Kod örnekleri Kart gerekmez

Next.js MQTT entegrasyonu ilk başta kafa karıştırıcıdır, çünkü Next.js kodunuzu birkaç farklı yerde çalıştırır: tarayıcıda, bir Node.js sunucusunda, serverless fonksiyonlarda ve bazen edge'de. Bu rehberde bu yerlerden hangisinin bir MQTT bağlantısını tutabileceğini öğrenecek ve neredeyse her projeyi kapsayan üç deseni kuracaksınız: güvenli WebSocket üzerinden subscribe olan bir client component, sunucu tarafı kimlik bilgileriyle publish yapan bir route handler ve bir Server-Sent Events köprüsü. Tüm örnekler ConnectMQ'nun broker.connectmq.com üzerindeki WebSocket uç noktasını ve App Router ile MQTT.js v5'i kullanır.

Next.js'te MQTT Nerede Çalışabilir?

MQTT uzun ömürlü bir bağlantıya ihtiyaç duyar. Nereye uyduğunu bu tek gerçek belirler:

Çalışma ortamı Uzun ömürlü MQTT bağlantısı? Aktarım Tipik kullanım
Tarayıcı (client component) Evet, sekme açık olduğu sürece wss:// port 8084, yol /mqtt Canlı paneller, cihaz kontrolleri
Node.js sunucusu (next start, container, VM) Evet mqtts:// 8883 veya wss:// 8084 Paylaşılan subscriber, SSE köprüsü, publish
Serverless fonksiyon Hayır, yalnızca istek başına mqtts:// 8883 Tek bir komut publish edip bağlantıyı kesmek
Edge runtime Hayır MQTT.js TCP için desteklenmez MQTT için kaçının
Server Components Hayır, bir kez render edilirler - Bunun yerine veriyi veritabanından okuyun

Bir Server Component render edilir ve biter; bir aboneliği canlı tutamaz. Server Actions da istek kapsamlıdır. Dolayısıyla asıl seçim, tarayıcıdan bağlanmak ile Node.js üzerinde çalışan bir sunucu route'undan bağlanmak arasındadır.

İstemci kütüphanesini bir kez kurun:

npm install mqtt

MQTT.js v5 hem Node.js hem de tarayıcı derlemesiyle gelir ve bundler'lar doğru olanı otomatik olarak seçer. Kütüphanenin kendisini MQTT.js Node.js rehberi ele alır; burada Next.js'e nasıl oturduğuna odaklanıyoruz.

Desen 1: Client Component'te WebSocket Üzerinden Subscribe

Canlı paneller için tarayıcı doğrudan broker'a bağlanır. Arada sunucu adımı yoktur, gecikme minimumdur ve Next.js sunucunuz mesaj başına hiçbir iş yapmaz. Tarayıcılar ham TCP açamaz, bu nedenle bu yöntem her zaman güvenli WebSocket üzerinden MQTT kullanır. Aktarım katmanını WebSocket üzerinden MQTT yazımız açıklıyor.

app/hooks/useMqtt.js içinde yeniden kullanılabilir bir hook oluşturun:

"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 };
}

Bunu bir sayfada, app/live/page.jsx içinde kullanın:

"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, JSX içindeki metni escape eder; bu yüzden payload'ları bu şekilde render etmek script enjeksiyonuna karşı güvenlidir. Bir terminalden test ölçümü publish edin ve ekranda belirdiğini izleyin:

mosquitto_pub -h broker.connectmq.com -p 1883 -i next-test-3b1c -t 'connectmq/demo/next-k7f3q2/sensors/room1' -m '{"t":22.4}'

Geliştirme sırasında can yakan üç ayrıntı

  1. Strict Mode çift mount. Geliştirme ortamında React, cleanup hatalarını ortaya çıkarmak için bileşenleri mount eder, unmount eder ve yeniden mount eder. Effect cleanup içinde client.end() yoksa iki bağlantı elde edersiniz. Client ID'yi effect içinde üretmek de iki örneğin aynı ID için çekişmesini önler.
  2. Asla modül kapsamında bağlanmayın. Bir client dosyasındaki üst düzey mqtt.connect() çağrısı, o bileşenin sunucu tarafı render'ı sırasında da çalışır ve sunucunuzda bir TCP bağlantısı açar. Bağlantıları yalnızca tarayıcıda çalışan useEffect içinde tutun.
  3. NEXT_PUBLIC_ herkese açık demektir. Bu öneke sahip her değişken JavaScript bundle'ına gömülür. Oraya yalnızca broker URL'sini koyun, asla bir parola koymayın.

Desen 2: Özel Kimlik Bilgileriyle Route Handler'dan Publish

Bir vanayı açmak veya bir cihazı yeniden başlatmak gibi fiziksel dünyayı değiştiren komutlar, her ziyaretçinin okuyabileceği kimlik bilgileriyle gönderilmemelidir. Bunun yerine tarayıcı API'nizi çağırır, sunucu kullanıcıyı doğrular ve ortam değişkenlerinde tutulan kimlik bilgileriyle publish yapar.

Önce lib/mqtt-server.js içinde sunucu tarafı bir singleton oluşturun. İstemciyi globalThis üzerinde önbelleğe almak, geliştirme sırasında hot reload'un her dosya kaydında yeni bir bağlantı oluşturmasını engeller:

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;
}

Ardından app/api/command/route.js içindeki route handler:

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 });
  }
  // Publish etmeden önce kullanıcının oturumunu veya rolünü burada kontrol edin.

  await getMqttClient().publishAsync(
    `${PREFIX}/devices/${deviceId}/cmd`,
    JSON.stringify({ action, at: new Date().toISOString() }),
    { qos: 1 }
  );
  return Response.json({ ok: true });
}

Özel bir ConnectMQ broker'ı için .env.local dosyanız şöyle görünür:

MQTT_URL=mqtts://YOUR_BROKER_HOST:8883
MQTT_USERNAME=YOUR_USERNAME
MQTT_PASSWORD=YOUR_PASSWORD
MQTT_CLIENT_ID=YOUR_CLIENT_ID

deviceId değerini katı bir desene göre doğrulamak isteğe bağlı değildir. Kullanıcı girdisinin doğrudan bir topic'e akmasına izin verirseniz, +, # veya fazladan eğik çizgi içeren bir istek hiç amaçlamadığınız topic'lere publish yapabilir.

Serverless platformlarda

Serverless barındırmada bir fonksiyon örneği istekler arasında dondurulabilir veya atılabilir; bu yüzden önbelleğe alınmış bir bağlantıya güvenilemez. Bunun yerine aynı istek içinde bağlanın, publish edin ve kapatın:

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 });
}

Bu, her isteğe bir TLS el sıkışması ekler; ara sıra gönderilen komutlar için sorun değildir ancak yüksek frekanslı publish için uygun değildir. ConnectMQ'nun özel broker'larda client ID'leri verilen kimlik bilgilerine bağladığını unutmayın; çok sayıda kısa ömürlü fonksiyonu eş zamanlı çalıştırıyorsanız, kimlik bilgilerinizin ve client ID'lerinizin portalda nasıl sağlandığını kontrol edin.

Desen 3: Server-Sent Events ile MQTT'yi Tarayıcıya Aktarmak

Bazen tarayıcının broker ile hiç konuşmasını istemezsiniz; örneğin topic verisinin kullanıcı başına filtrelenmesi gerektiğinde. Bir Node.js route'u tek bir abonelik tutabilir ve mesajları Server-Sent Events (SSE) üzerinden tarayıcılara dağıtabilir. Bu yalnızca uzun süre çalışan bir Node.js sunucusunda işe yarar, kısa zaman aşımlı serverless fonksiyonlarda çalışmaz.

Singleton içinde bir kez subscribe olun (bunu getMqttClient içinde istemciyi oluşturduktan sonra ekleyin):

globalForMqtt.__mqttClient.on("connect", () => {
  globalForMqtt.__mqttClient.subscribe("connectmq/demo/next-k7f3q2/sensors/#", { qos: 1 });
});

Ardından app/api/stream/route.js dosyasını ekleyin:

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" },
  });
}

Bir client component içinde new EventSource("/api/stream") her mesajı bir olay olarak alır. Artık tek bir broker bağlantısı tüm izleyicilere hizmet eder ve bu da sizi bağlantı limitlerinin rahatça içinde tutar.

Next.js MQTT Bağlantı Sorunlarını Giderme

MQTT'yi bir Next.js projesine bağlarken bazı sorunlar tekrar tekrar karşınıza çıkar:

  • Tarayıcı konsolunda "WebSocket connection failed". URL'yi dikkatle kontrol edin: wss:// olmalı, port 8084 olmalı ve /mqtt ile bitmelidir. HTTPS üzerinden sunulan bir sayfa düz ws:// kullanamaz.
  • Derleme sırasında "Module not found: net" veya "tls". Bir şey, genellikle Node.js modüllerini de import eden paylaşılan bir dosya aracılığıyla, yalnızca sunucuya ait kodu client bundle'ına çekmiştir. lib/mqtt-server.js dosyasını "use client" ile işaretlenmiş hiçbir şeyin içine almayın.
  • Route handler takılıyor, sonra zaman aşımına uğruyor. Broker girişi reddetmiştir veya port sunucunuzdan engellenmiştir; bu yüzden publishAsync hiç gelmeyecek bir bağlantıyı bekler. error olayını dinleyip loglayın ve aynı kimlik bilgilerini sunucudan mosquitto_pub ile test edin.
  • Mesajlar sunucuda iki kez geliyor. Singleton globalThis üzerinde önbelleğe alınmadığı için hot reload ikinci bir istemci oluşturmuştur. Düzelttikten sonra geliştirme sunucusunu yeniden başlatın.

Doğru Deseni Seçmek

Karar vermek için bu kontrol listesini kullanın:

  1. Salt okunur canlı veri, herkese açık veya düşük hassasiyetli: Desen 1, salt okunur kimlik bilgileriyle WebSocket üzerinden tarayıcı.
  2. Yan etkisi olan eylemler: Desen 2, sunucu tarafı kimlik bilgileri ve girdi doğrulaması içeren route handler.
  3. Kullanıcı başına filtreleme veya küçük bir planda çok sayıda izleyici: Desen 3, Node.js sunucusunda SSE köprüsü.
  4. Geçmiş verisi grafikleri: Yukarıdakilerin hiçbiri tek başına yetmez. Mesajları ayrı bir subscriber ile saklayın ve veritabanını bir Server Component'ten sorgulayın.

Çoğu üretim uygulaması 1 ve 2'yi birleştirir: tarayıcı canlı güncellemeler için subscribe olur ve her komut bir route handler üzerinden geçer. Bileşen düzeyindeki ayrıntılar için React MQTT eğitimine, tipli payload'lar için TypeScript ile MQTT rehberine göz atın. MQTT sohbet uygulaması eğitimi tamamen tarayıcı tarafında çalışan bir tasarımın iyi bir örneğidir; yığınınızın bir kısmı başka bir dildeyse MQTT istemci kütüphanelerine genel bakış yardımcı olur. Resmi Next.js route handler dokümantasyonu çalışma ortamı seçeneklerini derinlemesine ele alır.

Next.js Uygulamanızı ConnectMQ'ya Bağlayın

ConnectMQ, client component'ler için 8084 portunda güvenli WebSocket ve route handler'lar için 8883 portunda TLS sunan tam yönetilen bir MQTT hizmetidir; böylece bir Next.js uygulamasının her iki tarafı da aynı broker ile konuşur. Prototipinizi herkese açık MQTT test broker'ı üzerinde geliştirin, ardından özel bir bulut MQTT broker'ına geçin. Serverless planında ücretsiz kaydolun, kredi kartı gerekmez.

Sık Sorulan Sorular

Next.js Server Components içinde MQTT kullanabilir miyim?

Canlı veri için hayır. Server Components istek başına bir kez render edilir ve bir aboneliği tutamaz. Bir client component'te veya Node.js route handler'ında subscribe olun, saklanan veriyi okumak için de Server Components kullanın.

MQTT, Next.js Edge runtime üzerinde çalışır mı?

Edge runtime, MQTT.js'in TCP ve TLS için kullandığı Node.js ağ API'lerini sağlamaz. Broker ile konuşan her route'ta export const runtime = "nodejs" ayarlayın.

Next.js'te MQTT kimlik bilgilerini tarayıcıdan nasıl uzak tutarım?

Bunları herkese açık olmayan ortam değişkenlerinde saklayın ve yalnızca route handler'larda veya sunucu kodunda kullanın. NEXT_PUBLIC_ önekli her şey client bundle'ına girer.

Geliştirme sırasında neden iki MQTT bağlantısı görüyorum?

React Strict Mode geliştirme ortamında bileşenleri iki kez mount eder ve Next.js hot reload sunucu modüllerini yeniden çalıştırır. İstemciyi effect cleanup içinde kapatın ve sunucu istemcilerini globalThis üzerinde önbelleğe alın.

İlgili yazılar

Test, Betik ve Hata Ayıklama için En İyi MQTT CLI Araçları

mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, MQTT.js CLI, curl ve mqttui'nin uygulamalı karşılaştırması; betik yazma ve hata ayıklama için doğru aracı seçebilmeniz için aynı test broker.connectmq.com üzerinde çalıştırıldı.

7 dk okuma

Tüm yazılar →

Özel MQTT broker’ınız birkaç tık uzağınızda

Hesabınızı oluşturun, broker kimlik bilgilerinizi alın ve ilk cihazınızı bağlayın — kurulacak, yamalanacak ya da ölçeklendirilecek sunucu yok.