How to embed a Changelog Podcast Player into a React app?
Tags: react, ssr, tutorial, nextjs
Published: 2020-07-16 14:43:41

## Introduction

I recently had a chance to do a small redesign of my [personal site](https://kepinski.me). One of the goals, besides making everything look cleaner, was to embed [Changelog's](https://changelog.com/) Podcast Player, so that it would play [85th episode of JS Party](https://changelog.com/jsparty/85), in which I talked about one of my projects.

It seems pretty straight forward:

```html
<audio data-theme="night" data-src="https://changelog.com/jsparty/85/embed" src="https://cdn.changelog.com/uploads/jsparty/85/js-party-85.mp3" preload="none" class="changelog-episode" controls></audio><p><a href="https://changelog.com/jsparty/85">JS Party 85: Building PizzaQL at the age of 16</a> – Listen on <a href="https://changelog.com/">Changelog.com</a></p><script async src="//cdn.changelog.com/embed.js"></script>
```

And works just fine on a static HTML site. However, in my case, I was dealing with a React application, powered by [Next.js](https://nextjs.org/) with Static Site Generation (SSG). I also focused much on performance and accessibility.

That's exactly why I decided to make this tutorial!

## Tutorial

First of all, we need to embed an `audio` tag into our app. It's fairly easy:

```js
<audio
    controls
    data-theme="night"
    data-src="https://changelog.com/jsparty/85/embed"
    src="https://cdn.changelog.com/uploads/jsparty/85/js-party-85.mp3"
    preload="none"
    className="changelog-episode"
/>
```

> Make sure to replace `data-src` and `src` attributes so that they link to an episode, which you want to embed. **It does not need to be the JS Party podcast**.

---

We now need to load a script, which will add an `iframe` with the player itself. Normally, we would obtain it from https://cdn.changelog.com/embed.js, but instead, we are going to self-host it. Here are the reasons why:

- Self-hosting assets (like fonts or scripts) is significantly faster;
- The default Changelog's script does not add a `title` attribute to the created `iframe` - this will lower our site accessibility score on tools like [Lighthouse](https://developers.google.com/web/tools/lighthouse);
- The default Changelog's script sometimes throws `Uncaught TypeError: Cannot read property 'postMessage' of null.`.*

*This does not break the functionality of the player and therefore we can easily fix it using a [try...catch](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) statement.

In order to edit the embed script, we first need to unminify the original one from Changelog's CDN. We can then modify it like so:

```diff
!(function (e) {
    function t(t) {
        var r = t.getAttribute("data-src"),
            i = t.getAttribute("data-theme") || "night",
            n = e.createElement("iframe");
        n.setAttribute("src", r + "?theme=" + i + "&referrer=" + e.location.href),
            n.setAttribute("width", "100%"),
            n.setAttribute("height", "220"),
            n.setAttribute("scrolling", "no"),
            n.setAttribute("frameborder", "no"),
+           n.setAttribute("title", "Podcast"),
            t.parentNode.replaceChild(n, t),
            (this.id = +new Date()),
            (this.src = n.src),
            (this.iframe = n);
    }
    var r = "https://changelog.com",
        i = e.getElementsByClassName("changelog-episode"),
        n = [],
        a = function (e, t) {
-            (t.context = "player.js"), (t.version = "0.0.11"), (t.listener = e.id), e.iframe.contentWindow.postMessage(JSON.stringify(t), r);
+            try {
+                (t.context = "player.js"), (t.version = +"0.0.11"), (t.listener = e.id), +e.iframe.contentWindow.postMessage(JSON.stringify(t), r);
+            } catch {}
        };
    window.addEventListener("message", function (e) {
        if (e.origin !== r) return !1;
        var t = JSON.parse(e.data);
        if ("player.js" !== t.context) return !1;
        if ("ready" === t.event) for (var i = n.length - 1; i >= 0; i--) n[i].src === t.value.src && a(n[i], { method: "addEventListener", value: "play" });
        if ("play" === t.event) for (i = n.length - 1; i >= 0; i--) n[i].id !== t.listener && a(n[i], { method: "pause" });
    });
    for (var s = i.length - 1; s > -1; s--) n.push(new t(i[s]));
})(document);

``` 

---

We are going to load the script in a `useEffect` hook so that it won't run on the server (where a browser API, like `document` and `window`, isn't available):

```js
useEffect(() => {
	const script = document.createElement('script');
	script.src = '/embed.js'; // Since I'm using Next.js I can place the file in the `public` folder and refer to it like so.
	script.async = true; // Make sure the script isn't render-blocking.
	script.defer = true;
	document.body.append(script);
}, []);
```

That's it! You now have a beautiful player on your site.

## Credits

The approach of using the `useEffect` hook was taken from [React NodeGui website source code](https://github.com/nodegui/react-nodegui/blob/8465d3de6381651bfa6a88a82d3cf8acf3681bff/website/src/components/Talks.js#L15-L20).

---

Thank you for reading my post! I hope you will find it useful ;)