Implementing the Preview Protocol

Illustrated drawing of Amadeus Maxmimilian StadlerIllustrated drawing of Amadeus Maxmimilian Stadler

Amadeus Stadler

Last updated February 17, 2023

After learning how real time content previews work in Mattrbld and what constraints and limitations you should be aware of, you will learn how to implement the preview protocol in your own project in this article. At the end of it, you should have a fully working real-time preview of your content right in the CMS.

Prerequisites

For the preview to work, you need a dedicated route for it in your project. A common convention is to have it under https://example.com/___preview/, but you can place it wherever you want. This route needs to be accessible by your Mattrbld instance of choice and embeddable into an iFrame. It will host an HTML document that will be used to render the content passed from Mattrbld. This is your preview page.

It’s recommended to use a web-server with live-reloading while you’re developing the preview page, so you can immediately see the changes you make reflected in Mattrbld. Preview-URLs running under localhost are supported.

Once a Preview-URL was added in the Project Settings, the content editor of any collection will show a “Show Preview” button in the top toolbar next to the “Save” button.

As of version 0.4.0 you can manage the availability of previews on a per-collection basis. If the “Show Preview” button doesn’t show up for you, it may be because the previews were disabled for the Collection the item you’re editing is in.

Clicking this button will change the content area to a split-screen view, with your content on the left and the preview on the right. The preview area is an iFrame loading your custom preview page.

Once everything is implemented correctly, changing the content will update the preview. However, a bidirectional connection between Mattrbld and the website running in the preview pane will have to be established first. This is made possible through the window.postMessage method. You can learn more about it here.

The Handshake

As soon as the page in the preview pane has loaded, Mattrbld sends a first postMessage to it containing the following data:

{
  handshake: 'p7s8n1c', // random 7 character code
}

It then waits for a return of this handshake and, optionally, an Array of extra features the preview page implements. To handle this handshake and establish a connection, you can add the following code to your preview page:

// replace this with the URL of your Mattrbld instance if you're using a custom one!
const MATTRBLD_INSTANCE = "http://app.mattrbld.com/";
const cachedSource = null;

function handleMessage(e) {
  // get the relevant details from the message
  const { origin, data, source } = e;

  if (origin !== MATTRBLD_INSTANCE) {
    // handle postMessages from other origins
    // usually by throwing an error
    return;
  }

  // cache the message source to send data back
  cachedSource = source;

  // handle the handshake
  if (data && data.handshake) {
    cachedSource.postMessage(
      {
        handshake: data.handshake,
        // include optional features here
        // but only if implemented!
        supports: [],
      },
      MATTRBLD_INSTANCE
    );
  }
}

// handle messages coming from Mattrbld
window.addEventListener('message', handleMessage);

// needed to signal Mattrbld that the page has loaded if it was opened in a separate tab / window
if (window.opener) {
  window.opener.postMessage(
    { loaded: true }, MATTRBLD_INSTANCE
  );
}

As of version 0.4.0, Mattrbld supports the optional preview features, which require extra methods to be implemented in the preview page. To activate them, you can pass their names in the supports-Array. To keep this example simple, it implements only the required features, hence why the array is empty.

With this code in place, clicking the “Show Preview” button should open the preview, show a blank page, but also the following message: “Communication with the preview has been set up successfully”.

With that, the preview page is ready to receive actual data, which Mattrbld will start sending immediately.

Preview Data

The general preview data Mattrbld sends after the handshake and after every change to the content, has the following format:

{
  // the name of the collection the content is in
  collection: 'Pages.json',
  // the URL of the content item
  url: '/docs/implementing-the-preview-protocol/',
  // the content according to the Schema
  data: {…},
  // any Media Library images
  imageMap: <Map>
}

The url property will be compiled based on a number of factors:

  • Whether the Collection specifies a URL-template

  • Whether it is localised

  • Whether the content item is a draft or not

If these conditions aren’t met, the URL will instead be assembled based on the path within the project. In any case, depending on your build-process, the URL might not reflect the final, published URL.

The data property generally contains all the data of your content according to the Schema assigned to the content item. This is what you will use to actually render the preview for your project.

Handling Messages from Optional Features

Mattrbld content previews support additional features, such as comments. These are optional, since they require extra implementations on in the preview page and may not be applicable to all types of projects. You can find implementation details about these features in their respective documentation articles.

If these features are enabled, Mattrbld will send additional information depending on the feature. To distinguish these messages from regular content updates, they include a feature property with the name of the feature:

// inside the handleMessage() function
if (data && data.handshake) {
  // handle handshake
} else if (data && data.feature === 'comments') {
  // handle comment-feature related messages e.g.:
  switch (data.type) {
    case 'modechange':
      // handle modechange event
      break;
    default:
      break;
  }
} else if (data) {
  // render content
}

Rendering Strategies

Rendering the data you receive from Mattrbld onto your preview page is up to you—and the primary reason why this preview system is so flexible. The complete example below will simply render the data as JSON into a <pre> element, which allows you to clearly see the data that is passed. This obviously isn’t very useful beyond demoing the protocol, but here are some pointers of other ways you could render the data:

  • If you’re building a website with a component-based framework such as Svelte, Vue, or React, you could render the page as you would in the rest of the application, but using the collection property to decide which component should be handling the data

  • If you’re building a simple blog using Markdown files, you could transform the data into Markdown using a client-side Markdown-parser and inject the HTML into an output element that’s embedded into your normal post layout on the preview page

  • You could use a client-side templating engine to render the data using templates and display the resulting HTML

In the end, the only limit is your imagination! (Well, and the capabilities of the browser—but browsers these days are very powerful.)

A Complete Example

To help you get started, here is a complete (albeit basic) implementation of the preview protocol you can use as your preview page:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Preview Protocol Tester</title>
</head>
<body>
  <h1>Mattrbld Preview Protocol Tester</h1>
  <pre>Ready for output.</pre>
  <script>
    // replace this with the URL of your Mattrbld instance if you're using a custom one!
    const MATTRBLD_INSTANCE = "http://app.mattrbld.com/";
    const output = document.body.querySelector('pre');

    function handleMessage(e) {
      // get the relevant details from the message
      const { origin, data, source } = e;

      if (origin !== MATTRBLD_INSTANCE) {
        output.innerText = `The message came from an unallowed origin: ${origin}`;
        return;
      }

      // handle the handshake
      if (data && data.handshake) {
        source.postMessage(
          {
            handshake: data.handshake,
            supports: [],
          },
          MATTRBLD_INSTANCE
        );
      } else {
        // Render the received data
        output.innerText = JSON.stringify(data, null, 2);
    }

    // handle messages coming from Mattrbld
    window.addEventListener('message', handleMessage);

    // needed to signal Mattrbld that the page has   loaded if it was opened in a separate tab / window
    if (window.opener) {
      window.opener.postMessage(
        { loaded: true }, MATTRBLD_INSTANCE
      );
    }
  </script>
</body>

Since we are not converting the imageMap property to anything, it will show up as {} when displayed with JSON.stringify(). This is intentional in this example, since it contains binary Blobs that wouldn’t translate well to being displayed as text. In your own implementation, you may want to convert the imageMap to ObjectURLs in order to display them as images, as described in the constraints and limitations article.


In this article, you learned how to create a preview page that you can link to from your Mattrbld project to enable real-time previews for your content. The next articles will teach you how to implement specific optional features, such as comments. If you’re not interested in them, you can instead skip to the final article of this section, where you will learn about some common issues with the preview functionality and how to fix them.