Inline YouTube Videos

Adding Inline YouTube Videos

Adding Inline YouTube Videos | Blog

Adding Inline YouTube Videos

In my Gatsby blog, I had a few posts that contained an inline YouTube video. Adding this functionality took me some time on Gatsby (definitely a me problem), but getting it working on Astro took more time still. In fact I was not on top of the problem by the time I switched the traffic over from the Gatsby blog to the Astro blog and this post is going to talk about how I fixed that problem.

Where we started

At present, I have a few older posts with something like this:


---
title: "Did the pandemic kill the Surface Duo line?"
description: "Microsoft has ditched the Surface Duo, but why?"
image: 
  url: "/img/surface-duo-1.png"
  alt: "Surface Duo"
draft: false
---

*blah blah blah* Link to video:

`youtube: https://www.youtube.com/watch?v=n8CCRdQxaEE`

On the old Gatsby blog this looked like so:

An inline YouTube video

However, in my current Astro blog, that same post looked (it has been fixed by the time this blog post was published) like this:

A broken inline YouTube video

Let’s try this

My first attempt involved creating a custom Remark plugin to process the youtube: syntax in my Markdown files, similar to how it worked in Gatsby.

I created a remark-youtube.js file with the following content:


import { visit } from 'unist-util-visit';

export default function remarkYoutube() {
  return (tree) => {
    visit(tree, 'paragraph', (node, index, parent) => {
      const textNode = node.children[0];

      if (textNode && textNode.type === 'inlineCode') {
        const { value } = textNode;
        const youtubePrefix = 'youtube: ';

        if (value.startsWith(youtubePrefix)) {
          const url = value.slice(youtubePrefix.length).trim();
          const idMatch = url.match(/(?:youtube\.com\/watch\\?v=|youtu\.be\/)([^\s&]+)/);

          if (idMatch) {
            const videoId = idMatch[1];

            const iframeHTML = `
              <div class="video-responsive">
                <iframe
                  src="https://www.youtube.com/embed/${videoId}"
                  frameborder="0"
                  allowfullscreen
                ></iframe>
              </div>`;

            const iframeNode = {
              type: 'html',
              value: iframeHTML,
            };

            parent.children.splice(index, 1, iframeNode);
          }
        }
      }
    });
  };
}

Of course, I’d need to add this custom plugin to my astro config:


import remarkYoutube from './plugins/remark-youtube.js';

export default defineConfig({
  // ... other configurations ...
  markdown: {
    remarkPlugins: [remarkYoutube],
    // ... other markdown configurations ...
  },
});

Yet for some reason, that still escapes me, I couldn’t get the YouTube embeds to display. The Markdown files were processed without errors, but the expected iframes weren’t rendering on the page. I tried a few variations, I asked a couple of friendly LLM’s which sent me all over the place with some interesting ideas, but it just didn’t work. I probably missed something, but one of the ideas the friendly LLM’s offered up was to use mdx files.

The mdx file

This method involves converting the Markdown files to MDX and leveraging React components within them. The primary benefit is that I can use JSX directly inside the Markdown files.

Begin with a new component ./src/components/YouTube.jsx:


import React from 'react';

const YouTube = ({ id }) => (
  <div className="video-responsive">
    <iframe
      src={`https://www.youtube.com/embed/${id}`}
      frameBorder="0"
      allowFullScreen
    ></iframe>
  </div>
);

export default YouTube;

Which is basically the same iframe code from earlier. We are taking the Video ID and passing it to the iFrame. To use this, we need to change the file extension for all the blog posts that have inline YouTube videos, luckily for me, that is not a huge number. We then modify the file as follows:


---
title: "Did the pandemic kill the Surface Duo line?"
description: "Microsoft has ditched the Surface Duo, but why?"
image: 
  url: "/img/surface-duo-1.png"
  alt: "Surface Duo"
draft: false
---

import YouTube from '../../components/YouTube.jsx';

*blah blah blah* Link to video:

<YouTube id="v=n8CCRdQxaEE" />

Notice the import statement and <youtube id />. The import statement calls our new component and the <youtube /> passes the Video ID of the video we want to show. I think we might be able to do some more things with this, but for now, it ticks the boxes.

Styling

There was one minor issue where the inline video was very small, the embedded video was there, but it was tiny, so I also added some styling to my MarkdownPostLayout.astro file which set an initial size on wider screens offering a mobile friendly size on smaller screens.

Fixing old stuff

Now, if you haven’t read any of the older articles, you didn’t see this problem anyway, however part of fixing this involved retrospectively updating previous files, so if you do happen to check an older post containing a video, it should all be working fine at the time of this post going live.

Any Gotcha’s ?

Mostly this change went without issue, however, by converting a regular Markdown file to a mdx file, we do have to be careful with some of the content within. For example, I have this in one blog post:


I then defined conditional formatting to the Risk Score cell on the following numbers:

* Green = <= 30
* Yellow = 40 - 74
* Red = >= 75

In MDX files, the parser interprets < as the beginning of an HTML or JSX element. When it encounters something like </=, it expects a valid tag name after < or </, but = is not a valid character to start a tag name. This was an easy fix, but it did surprise me at the time:


I then defined conditional formatting to the Risk Score cell on the following numbers:

* Green = `<= 30`
* Yellow = `40 - 74`
* Red = `>= 75`

Using those backticks fixed this for me.