Using Mercure and Hotwire to Stream Page Changes
Hotwire is an alternative approach to building modern web applications without using much JavaScript by sending HTML instead of JSON over the wire.
Hotwire comes with a handy feature called Turbo Streams. Turbo Streams allow servers to push page changes to connected clients in real-time.
Using Mercure to power a Turbo Stream is straightforward, and doesn't require any external dependency:
import { connectStreamSource } from "@hotwired/turbo"; // The "topic" parameter can be any string or URI const es = new EventSource( "https://example.com/.well-known/mercure?topic=my-stream", ); connectStreamSource(es);
The native EventSource
class is used to connect to the Server-Sent Events (SSE) endpoint exposed by the Mercure hub.
Broadcasting Page Changes With Mercure
To broadcast messages through Turbo Streams, simply send a POST
HTTP request to the Mercure hub:
curl \ -H 'Authorization: Bearer <snip>' \ -d 'topic=my-stream' \ -d 'data=<turbo-stream action=...' \ -X POST \ https://example.com/.well-known/mercure
topic
must be the same topic we used in the JavaScript code ;data
contains the Turbo Streams messages to broadcast ;the
Authorization
header must contain a valid publisher JWT.
Other Mercure features, including broadcasting private updates to authorized subscribers, are also supported.
In this example, we use curl
but any HTTP client or Mercure client library will work.
Disconnecting From a Stream
To disconnect the stream source, use the disconnectStreamSource()
function:
import { disconnectStreamSource } from "@hotwired/turbo"; disconnectStreamSource(es);
Creating a Stimulus Controller
Mercure also plays very well with the Stimulus framework (another component of Hotwire).
In the following example, we create a Stimulus controller to connect to the SSE stream exposed by the Mercure hub when an HTML element is created, and to disconnect when it is destroyed:
// turbo_stream_controller.js import { Controller } from "stimulus"; import { connectStreamSource, disconnectStreamSource } from "@hotwired/turbo"; export default class extends Controller { static values = { url: String }; connect() { this.es = new EventSource(this.urlValue); connectStreamSource(this.es); } disconnect() { this.es.close(); disconnectStreamSource(this.es); } }
<div data-controller="turbo-stream" data-turbo-stream-url-value="https://example.com/.well-known/mercure?topic=my-stream" > <!-- ... --> </div>