Tutoriales

MQTT en el navegador con JavaScript puro (sin build)

Carga MQTT.js 5 desde un CDN en un único archivo HTML y conéctate por WebSockets seguros: publica, suscríbete, reconecta y depura sin npm ni bundler.

Actualizado · 8 min de lectura

MQTT en el navegador con JavaScript puro (sin build)

No necesitas npm, React ni un bundler para hablar MQTT desde una página web. MQTT.js 5 incluye un bundle listo para el navegador: añade una etiqueta <script>, llama a mqtt.connect('wss://…') y la página publica y se suscribe por WebSockets seguros. Esta guía construye un cliente completo en un solo archivo que puedes abrir desde el disco, subir a cualquier hosting estático o pegar en una herramienta interna.

Por qué los navegadores necesitan WebSockets

Los navegadores no pueden abrir sockets TCP directos, así que mqtt://host:1883 y mqtts://host:8883 quedan fuera del alcance de una página web. Los brokers lo resuelven con un listener WebSocket: los mismos paquetes MQTT viajan dentro de frames WebSocket. Para broker.connectmq.com los endpoints son:

URLUso
ws://broker.connectmq.com:8083/mqttWebSocket sin cifrar, solo para páginas servidas por http:// o file://
wss://broker.connectmq.com:8084/mqttWebSocket seguro (TLS), funciona desde cualquier página; úsalo por defecto

La ruta /mqtt importa: conectarse al puerto correcto con la ruta equivocada suele fallar durante el upgrade HTTP (consulta error 404 en la ruta WebSocket). MQTT sobre WebSockets explica el transporte en detalle.

La página completa

Guarda esto como index.html. Fija una versión exacta de MQTT.js desde el CDN, que expone un objeto global mqtt.

index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>MQTT browser client</title>
  <script src="https://unpkg.com/mqtt@5.10.1/dist/mqtt.min.js"></script>
  <style>
    body { font: 15px/1.5 system-ui, sans-serif; max-width: 640px; margin: 2rem auto; padding: 0 1rem; }
    #log { background: #111; color: #9f9; padding: .75rem; height: 260px; overflow: auto; font-family: monospace; }
    input, button { font: inherit; padding: .4rem .6rem; }
  </style>
</head>
<body>
  <h1>MQTT over WSS</h1>
  <p>Status: <strong id="status">connecting…</strong></p>
  <form id="pub">
    <input id="msg" placeholder="Message" required>
    <button>Publish</button>
  </form>
  <pre id="log"></pre>

  <script>
    const URL = 'wss://broker.connectmq.com:8084/mqtt';
    const TOPIC = 'testmqtt/browser-demo/' + Math.random().toString(16).slice(2, 8);

    const $ = (id) => document.getElementById(id);
    const log = (line) => {
      $('log').textContent += new Date().toLocaleTimeString() + '  ' + line + '\n';
      $('log').scrollTop = $('log').scrollHeight;
    };

    const client = mqtt.connect(URL, {
      clientId: 'web-' + Math.random().toString(16).slice(2, 10),
      // username: 'username', password: 'password',   // see "Credentials" below
      protocolVersion: 5,
      clean: true,
      keepalive: 30,
      connectTimeout: 10_000,
      reconnectPeriod: 2_000,
    });

    client.on('connect', () => {
      $('status').textContent = 'connected';
      client.subscribe(TOPIC, { qos: 1 }, (err) => {
        log(err ? 'subscribe failed: ' + err.message : 'subscribed to ' + TOPIC);
      });
    });

    client.on('message', (topic, payload) => {
      log(topic + '  ' + payload.toString());
    });

    client.on('reconnect', () => { $('status').textContent = 'reconnecting…'; });
    client.on('offline',   () => { $('status').textContent = 'offline'; });
    client.on('error',     (err) => log('error: ' + err.message));

    $('pub').addEventListener('submit', (e) => {
      e.preventDefault();
      client.publish(TOPIC, $('msg').value, { qos: 1 });
      $('msg').value = '';
    });

    window.addEventListener('pagehide', () => client.end());
  </script>
</body>
</html>

Abre el archivo, escribe un mensaje y pulsa Publish: la página se suscribe a su propio topic aleatorio, así que el mensaje vuelve a través del broker y aparece en el log. Cada pestaña tiene su propio topic y client ID, por lo que dos pestañas abiertas no se interfieren. Para ver el tráfico desde fuera, abre el cliente MQTT online, suscríbete a testmqtt/browser-demo/# y publica desde cualquiera de los dos lados.

Variante con módulos ES

Si prefieres módulos, MQTT.js también publica un build ESM. No crea ninguna variable global, así que lo importas:

Script de módulo
<script type="module">
  import mqtt from 'https://unpkg.com/mqtt@5.10.1/dist/mqtt.esm.js';

  const client = mqtt.connect('wss://broker.connectmq.com:8084/mqtt', { protocolVersion: 5 });
  client.on('connect', () => client.publish('testmqtt/browser-demo/esm', 'hello from a module'));
</script>

Los scripts de módulo se difieren automáticamente y no filtran variables a window. Algunos navegadores bloquean los scripts de módulo en páginas abiertas desde file://, así que sirve la carpeta con un servidor web local:

Servir la carpeta en local
python3 -m http.server 8000
# después abre http://localhost:8000/

Opciones que importan en un navegador

OpciónRecomendadoPor qué
clientIdAleatorio por pestañaDos pestañas con el mismo ID se expulsan mutuamente en bucle
protocolVersion5 (o 4 para 3.1.1)MQTT 5 incluye reason codes en los errores, lo que facilita la depuración
keepalive30–60 sLos portátiles se suspenden y el Wi-Fi cambia; un keep-alive corto detecta antes los sockets muertos
reconnectPeriod2.000–5.000 ms0 desactiva la reconexión automática; valores muy pequeños saturan el broker
connectTimeout10.000 msFalla rápido y muestra un estado al usuario en lugar de cargar sin fin
cleantrueUna pestaña del navegador rara vez necesita una sesión persistente

Genera IDs con un prefijo que puedas reconocer en los logs del broker, o usa el generador de client ID. Si necesitas la misma configuración en otro lenguaje, el generador de código MQTT produce fragmentos equivalentes.

Credenciales en una página estática

Todo lo que hay en un archivo HTML es público: cualquiera puede ver el código fuente y copiar username y password. Trata las credenciales del navegador en consecuencia:

  • Crea un usuario del broker dedicado a la página y restríngelo con ACL exactamente a los topics que necesita. Un dashboard normalmente solo necesita lectura sobre .../telemetry/#.
  • Nunca reutilices en el navegador una cuenta de dispositivo, de backend o de administrador.
  • Para usuarios con sesión iniciada, deja que tu backend emita credenciales por usuario o de corta duración tras la autenticación y pásalas a mqtt.connect.

Autenticación MQTT por WebSocket en el navegador recorre estos patrones en detalle.

Depurar la conexión

Abre las DevTools (F12) y revisa dos sitios:

  1. Console. Aquí aparecen los errores del evento error y los mensajes WebSocket del propio navegador.
  2. Network → WS. Selecciona la conexión para ver el upgrade HTTP (estado 101 Switching Protocols si va bien) y los frames MQTT binarios en la pestaña Messages.
Mensaje en la consolaCausa probableSolución
Mixed Content: The page at 'https://…' was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint 'ws://…'Se usó ws:// desde una página HTTPSUsa wss://…:8084/mqtt
WebSocket connection to 'wss://…' failedPuerto o ruta incorrectos, firewall, problema de TLSRevisa el puerto 8084 y la ruta /mqtt; consulta la conexión WebSocket falla
connack timeoutEl WebSocket se abrió pero el CONNACK no llegó a tiempoBroker sobrecargado o un proxy que acumula datos; sube connectTimeout y reintenta
Connection refused: Not authorized / Bad User Name or PasswordCredenciales ausentes o incorrectasRevisa usuario, contraseña y las ACL del usuario
El estado alterna entre conectado y reconectandoClient ID duplicado en otra pestaña o dispositivoHaz clientId único por pestaña

La búsqueda de reason codes traduce los códigos numéricos que devuelven los brokers MQTT 5. Para descartar por completo tu página, prueba la misma URL en el cliente online: si allí conecta, el problema está en tu código, no en el broker ni en la red.

Mira el topic de la página demo en el cliente online

Próximos pasos

La página anterior es un cliente completo. Los siguientes pasos típicos son una interfaz de chat (un topic por sala, presencia con Last Will), un gráfico en vivo alimentado con telemetría de sensores o un panel de control que publica comandos. Cuando el código pase de unos cientos de líneas, migrar al paquete npm y a un bundler es fácil: la API mqtt.connect es idéntica, y por eso la guía de MQTT.js se aplica sin cambios.

Una etiqueta script, una URL wss:// y un puñado de manejadores de eventos es todo lo que un navegador necesita para hablar MQTT. Fija la versión de la librería, da a cada pestaña su propio client ID, envía al navegador solo una cuenta restringida y verifica cada cambio en las DevTools y en el cliente online antes de desplegar.

Preguntas frecuentes

¿Puede un navegador conectarse a MQTT en el puerto 1883 u 8883?

No. Los navegadores no pueden abrir sockets TCP directos, así que una página web solo llega a un broker MQTT a través de un listener WebSocket. Usa wss:// (puerto 8084 con la ruta /mqtt en broker.connectmq.com) desde cualquier página servida por HTTPS.

¿Es seguro poner credenciales MQTT en una página HTML estática?

Todo lo que hay en la página es legible por cualquier visitante. Usa un usuario del broker dedicado cuya ACL solo permita los topics que necesita esa página (a menudo solo lectura), o haz que tu backend entregue credenciales de corta duración. Nunca envíes al navegador una cuenta de administrador o de dispositivo.

¿Por qué mi página funciona en local pero falla al desplegarla?

Normalmente por contenido mixto. Una página servida por https:// no puede abrir una conexión ws:// insegura, así que el navegador la bloquea. Cambia la URL a wss:// en el puerto WebSocket con TLS y revisa la ruta.