Using Plyr in Gatsby Projects for Youtube & Vimeo

Posted: August 22nd, 2019

Topics: Gatsby, React, Plyr, useEffect

Video is always pretty annoying to deal with. So are iframes. I really like using Plyr for skinning 3rd party videos because it's extremely easy and the end result looks really nice -- good tradeoff in my opinion.

I was attempting to include the popular react-plyr package in my latest Gatsby project & was confronted with some general issues with rendering, as well as showstoppers at build-time.

I'm not sure exactly what was causing the rendering issues, but I try to avoid filing petty issues with open source projects. So, that left me to try to implement the core plyr package and try to make it work.

The other issue is simple. Plyr itself directly tries to manipulate dom elements like window and document, so we need to encapsulate it's require function somewhere safe during buildtime.

Here's a stipped down example of a musical artist page template from my new project:

import React, { useEffect } from 'react';
import _map from 'lodash/map';

import SEO from '../components/SEO';
import Container from '../components/Container';

import { PageWrapper, MediaWrapper } from '../components/Elements';

const Artist = ({ data }) => {
  /**
   * The `videos` array is being sourced by my page-level graphql
   * query.  This will simply be an array of Youtube video IDs.
   *
   * This is what we're going to use as the trigger for our useEffect
   * function.  Obviously with a static build this is most likely not going to
   * change, so it's essentially a componentDidMount lifecycle method.
   *
   * WHY?
   *
   * With Gatsby projects, the build process is taking place in a Node
   * environment, not a browser environment.  Anything that refrences the
   * `window` or `document` var is going to need to be encapsulated within
   * useEffect or a class-component lifecycle method.
   *
   * More on this:
   * https://www.gatsbyjs.org/docs/debugging-html-builds/
   */
  const { videos, seoMetaTags } = data.artist;

  /**
   * Basically, what we're going to do in the useEffect hook is:
   * A) Check that window & document are both defined so the builds work
   * B) Conditionally require the core plyr package if the condition is met
   * C) Look for any of our video classes on the page, then initialize them
   */

  useEffect(() => {
    if (typeof window !== 'undefined' && typeof document !== 'undefined') {
      const Plyr = require('plyr');
      Array.from(document.querySelectorAll('.js-player')).map(p => new Plyr(p));
    }
  }, [videos]);

  return (
    <PageWrapper>
      <SEO meta={seoMetaTags} />
      <Container>
        {_map(videos, v => {
          return (
            <MediaWrapper key={v.id}>
              <div
                className="js-player"
                data-plyr-provider="youtube"
                data-plyr-embed-id={v.youtubeId}
              />
            </MediaWrapper>
          );
        })}
      </Container>
    </PageWrapper>
  );
};

I was kind of surprised that this worked on the first try, but it did, and the performance is pretty good.

One last thing we're going to need to do is import the plyr styles. The way I opted to do it is to include a plyr.css file in my static folder, then import it in gatsby-browser.js like so:

import './static/fonts.css';
import './static/plyr.css';
import React from 'react';
import { ModalProvider } from './src/store/modalContext';

// eslint-disable-next-line react/prop-types
export const wrapRootElement = ({ element }) => {
  return <ModalProvider>{element}</ModalProvider>;
};

Thought this was kind of neat, hope you can use this for your own projects that require some fancy video action.