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:
| URL | Uso |
|---|---|
ws://broker.connectmq.com:8083/mqtt | WebSocket sin cifrar, solo para páginas servidas por http:// o file:// |
wss://broker.connectmq.com:8084/mqtt | WebSocket 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.
<!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 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:
python3 -m http.server 8000
# después abre http://localhost:8000/Opciones que importan en un navegador
| Opción | Recomendado | Por qué |
|---|---|---|
clientId | Aleatorio por pestaña | Dos pestañas con el mismo ID se expulsan mutuamente en bucle |
protocolVersion | 5 (o 4 para 3.1.1) | MQTT 5 incluye reason codes en los errores, lo que facilita la depuración |
keepalive | 30–60 s | Los portátiles se suspenden y el Wi-Fi cambia; un keep-alive corto detecta antes los sockets muertos |
reconnectPeriod | 2.000–5.000 ms | 0 desactiva la reconexión automática; valores muy pequeños saturan el broker |
connectTimeout | 10.000 ms | Falla rápido y muestra un estado al usuario en lugar de cargar sin fin |
clean | true | Una 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:
- Console. Aquí aparecen los errores del evento
errory los mensajes WebSocket del propio navegador. - Network → WS. Selecciona la conexión para ver el upgrade HTTP (estado
101 Switching Protocolssi va bien) y los frames MQTT binarios en la pestaña Messages.
| Mensaje en la consola | Causa probable | Solució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 HTTPS | Usa wss://…:8084/mqtt |
WebSocket connection to 'wss://…' failed | Puerto o ruta incorrectos, firewall, problema de TLS | Revisa el puerto 8084 y la ruta /mqtt; consulta la conexión WebSocket falla |
connack timeout | El WebSocket se abrió pero el CONNACK no llegó a tiempo | Broker sobrecargado o un proxy que acumula datos; sube connectTimeout y reintenta |
Connection refused: Not authorized / Bad User Name or Password | Credenciales ausentes o incorrectas | Revisa usuario, contraseña y las ACL del usuario |
| El estado alterna entre conectado y reconectando | Client ID duplicado en otra pestaña o dispositivo | Haz 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.


