Mercure.rocksSponsored by Les-Tilleuls.coop
Contribute!

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

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>