---
title: Hot reloading content in Craft CMS's Live Preview
description: Speed up your content authoring and ditch full-page refreshes for HMR-style updates.
pubDate: 2023-05-01
---

import Notice from '../../components/Notice.astro'

When I saw Jack Sleight's [post about hot reloading in Statamic's live preview](https://jacksleight.dev/posts/hot-reloading-statamic-s-live-preview-in-a-traditonal-mpa) I was pretty blown away. Live preview is already incredibly useful in a CMS, but inserting content changes _without_ a refresh would be a huge upgrade to that experience.

**A typical live preview experience works like this:**
1. A companion `<iframe>` is loaded, showing you the draft of your current entry
2. When a content change is made in the CMS the `<iframe>` is refreshed, showing the latest changes

Statamic 3.4.8 introduced the ability to disable the auto-refresh in favor of sending a **postMessage()** [event](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) via JavaScript. That means, the live preview experience changes a bit:

1. A companion `<iframe>` is loaded, showing you the draft of your current entry
2. When a content change is made the CMS doesn't refresh the `<iframe>`, but instead broadcasts a **postMessage()** event your site can **listen to** and **act upon**.

Once I saw this, I wanted to see if I could acheive a similar experience in Craft.

## Setting it up in Craft

<Notice>
  My example uses [Alpine.js](https://alpinejs.dev/) and the (excellent) [Morph plugin](https://alpinejs.dev/plugins/morph) to perform the DOM diff and update for the content swap. However, you can substitute this with another DOM morphing library of your choice.
</Notice>

1. We need to **disable auto-refresh** on any Preview Targets within our entry types. We no longer want the `<iframe>` to get a full-page reload.

2. Now need to register some JavaScript in the control panel that will fire our **postMessage()** event when the content has changed. I'm not going to walk through this process, but this is where you would likely [create a Craft CMS module](https://craftcms.com/docs/4.x/extend/module-guide.html) and, on control panel requests, insert the **postMessage()** JavaScript code:

```js
// Use Garnish to hook into Craft's beforeUpdateIframe's event
Garnish.on(Craft.Preview, 'beforeUpdateIframe', function (event) {
  if (!event.refresh) {
    // Once the content has been changed, fire a postMessage event with a live preview key,
    // but scope the broadcast to our site for security purposes
    event.target.$iframe[0].contentWindow.postMessage(
      'entry:live-preview:updated',
      event.previewTarget.url
    )
  }
})
```

3. In your front-end template—likely your **\_layout.twig** file—we need to add the following to _act_ upon the broadcasted **postMessage()**

```twig
{# Execute this JavaScript in preview mode only #}
{% if craft.app.request.isPreview %}
  {# Include Alpine.js and the Morph plugin #}
  <script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/morph@3.x.x/dist/cdn.min.js"></script>
  <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>

  <script>
    {# Listen for postMessage() calls #}
    window.addEventListener('message', async (event) => {
      {# Ignore messages that don't use our live preview key #}
      if (event.data !== 'entry:live-preview:updated') {
        return
      }

      {# Grab the URL we need to process from the event, send it to a fetch() call, and then parse the text #}
      const text = await fetch(event.target.location.href).then((res) => res.text())

      {# Process the text as HTML #}
      const updated = new DOMParser().parseFromString(text, 'text/html')

      {# Use Alpine.js's morph plugin to diff the existing DOM to the new HTML and update the page #}
      Alpine.morph(document.body, updated.body)
    })
  </script>
{% endif %}
```

And now, once you've opened your Live Preview panel in Craft CMS you'll be able to make changes without incurring a full-page refresh!

<video autoplay muted loop playsinline class="rounded-lg">
  <source src="/assets/hot-reloading-content-in-craft-cms-live-preview/live-reload.mp4" type="video/mp4" />
</video>

After a bit of setup it's an impressive experience! And, like [hot module reloading](https://medium.com/@dan_abramov/hot-reloading-in-react-1140438583bf), it's hard to go back once you've used it.
