Building a Video Blog with Gatsby and Markdown (MDX)

Publikováno: 15.1.2019

Since the dawn of the web, performance has been an important factor for web development because slow speeds often lead to a loss of visitors and, ultimately, depletion in revenue for commercial sit...

Celý článek

Since the dawn of the web, performance has been an important factor for web development because slow speeds often lead to a loss of visitors and, ultimately, depletion in revenue for commercial sites..

The demand for efficient web uploads brought about the rise of the JAMStack, which facilitates the design of interfaces and makes API calls to remote microservices and simple markups with JavaScript. The result? Enhanced security, reduced hosting cost, and lower requirements of programming skills for building web apps.

In the Internet, site generators abound for static, easily-deployable HTML, CSS, and JavaScript files. The evolution of those generators has seen technologies like Gatsby.js being employed for developing dynamic apps using the JAMStack, whose performance far outpaces that of static ones.

What You'll Build

This step-by-step tutorial shows you how to build and deploy a simple video blog with Gatsby.js 2.0, leverage React components in Markdown documents with MDX, and store and serve media assets with Cloudinary.

Benefits of Gatsby.js, MDX, and Cloudinary

Blogs are usually content heavy. Markdown enables easy creation of content, which is subsequently generated into HTML for the web. Gatsby is one such generator, transforming Markdown documents into HTML files that are served through dedicated routes.

Toward that end, Gatsby builds fast, static, progressive web apps with React.js components, querying data with GraphQL. In addition, Gatsby can, through plugins, pull in data from almost anywhere, including content management systems (CMSs), to populate your website.

Adding interactivity to the rather bland Markdown text would enhance the appeal. Enters MDX. With the Gatsby-MDX plugin, you can run MDX features on Gatsby sites and thus easily compose content in Markdown and build interactive user interfaces on your pages.

The Cloudinary Video Player affords you the ability to store and retrieve videos for all devices, either via a dynamic URL or through APIs. Storing videos on Cloudinary not only greatly reduces the bundle size of your apps but also ensures that those videos are served at an amazing speed and tailored for the devices in use.

Prerequisites

This tutorial requires proficiency in JavaScript.. Knowledge of React and Gatsby would be helpful but not required.

Installation

First, install Node.js and its package manager, NPM. To check if installation is complete, run this command:

npm -v && node -v

The output displays the Node.js version number.

Next, install the Gatsby-CLI tool. Gatsby ships a powerful CLI tool to help accelerate development of Gatsby apps. Type this command to install the latest stable version globally:

npm install -g gatsby-cli

Creation of Gatsby Site

Accompanying Gatsby are starters, which are templates that help you start building sites. (You can contribute starters, too!) Create a video blog using the Hello World starter. Create a new Gatsby project with:

gatsby new scotch-video-blog https://github.com/gatsbyjs/gatsby-starter-hello-world

This creates a directory with the project folder scotch-video-blog and we are ready to build out the site!

To see your Gatsby site, open your development server on port 8000 with this command:

gatsby develop

Gatsby uses GraphQL to query data. When the development server is running, GraphiQL - The GraphQL IDE (or playground as I like to call it) can be used to visualize the GraphQL queries. It runs on http://localhost:8001/___graphql.

To create a static site of just HTML, CSS and JS, run the build command:

gatsby build

Now install the other dependencies with this command:

npm install --save @mdx-js/mdx @mdx-js/tag cloudinary-core cloudinary-video-player gatsby-mdx gatsby-plugin-typography gatsby-source-filesystem jquery lodash react-typography typography typography-theme-bootstrap

Here's a description of each of the dependencies:

Below are the dependencies property values in package.json:

...
"dependencies": {
    "@mdx-js/mdx": "^0.15.5",
    "@mdx-js/tag": "^0.16.1",
    "cloudinary-core": "^2.5.0",
    "cloudinary-video-player": "^1.1.1",
    "gatsby": "^2.0.33",
    "gatsby-mdx": "^0.2.0",
    "gatsby-plugin-react-helmet": "^3.0.4",
    "gatsby-plugin-typography": "^2.2.2",
    "gatsby-source-filesystem": "^2.0.11",
    "jquery": "^3.3.1",
    "lodash": "^4.17.11",
    "react": "^16.5.1",
    "react-dom": "^16.5.1",
    "react-helmet": "^5.2.0",
    "react-typography": "^0.16.13",
    "typography": "^0.16.18",
    "typography-theme-bootstrap": "^0.16.18"
  }
...

Note the version numbers of the installed packages. Problems sometimes occur with new but unstable releases.

Creation of Directories

The Gatsby Hello World starter creates a pages subdirectory in the src directory. All JavaScript files under pages are transformed to HTML pages on build, as shown in the default index.js file.

Gatsby usually creates static pages with React components Now proceed to create folders for the components, blog posts, and utilities, such as typography config, in the src directory. Run the following command to go to src and create those three folders:

cd src && mkdir components posts utils

Next, set up the basic configurations for your site. See the next section.

Configuration of Plugins

Gatsby extends its functionality with plugins and there's just about a plugin for everything! In this tutorial, you configure them in a Gatsby-recognized file named gatsby-config.js in the root directory. Proceed to create that file in the root directory of your project folder.

Typography.js

For this project, we use the gatsby-plugin-typgraphy to provide global styling. The bootstrap theme installed earlier provides the Bootstrap default font style. In gatsby-config.js export an object in the module with a plugins property, as follows:

module.exports = {
    plugins:[
        {
            resolve: `gatsby-plugin-typography`,
            options:{
                pathToConfigModule: `src/utils/typography.js`
            }
        }
    ]
}

The above snippet uses the typography plugin and looks to the typography.js file for configuration. Specify that configuration as follows:

Go to the src/utils/ directory and create a typography.js file with the following content to create and export an instance of Typography with the Bootstrap theme:

import Typography from "typography";
import bootstrap from "typography-theme-bootstrap";

const typography = new Typography(bootstrap)

export default typography

Other Plugins

In similar fashion to gatsby-plugin-typography but without the configuration, configure each installed plugins in gatsby-config.js as follows:

//gatsby-config.js
module.exports = {
    plugins:[
        `gatsby-plugin-react-helmet`,
        {
            resolve: `gatsby-source-filesystem`,
            options: {
              name: `src`,
              path: `${__dirname}/src/`,
            },
        },
        {
            resolve: `gatsby-source-filesystem`,
            options:{
                name: `posts`,
                path: `${__dirname}/src/posts/`
            }
        },
        {
            resolve: `gatsby-plugin-typography`,
            options:{
                pathToConfigModule: `src/utils/typography.js`
            }
        },
        {
            resolve: `gatsby-mdx`,
            options: {
              defaultLayouts: {
                posts: require.resolve("./src/components/layout.js"),
                default: require.resolve("./src/components/layout.js")
              }
            }
        },
    ]
}

This configuration points to the files in src and posts the directories respectively according to gatsby-source-filesystem. Subsequently, the appropriate transformer plugin that is introduced can make use of the sourced files. Note that, as a time saver, gatsby-mdxhas been preconfigured with the default layouts for all the MDX files in this tutorial..

Next, follow the procedure in the next section to build the layout.

Creation of Layout

One of the beauties of Gatsby is that you can create a consistent layout across all the pages with a single component. Thanks to Gatsby, which can build a default layout from the file layout.js in the src/components directory, navigation bars and footer inconsistencies are now ancient history.

Here’s what to do: In src/components, create a layout.js file and a layout.css file to style layout.js. In layout.js, write a stateless React component like this:

import React from "react"
import {Link, StaticQuery, graphql} from 'gatsby'
import "./layout.css"

export default ({children})=> (
    <StaticQuery
    query={graphql`query {
            site {
                siteMetadata {
                    title
                }
            }
        }`}
    render={data => (
        <div>
            <div className="nav">
                <Link to={'/'} style={{textDecoration: 'none', color: 'inherit'}}>
                    <h1>{data.site.siteMetadata.title}</h1>
                </Link>
            </div>

            <div className="child">
                {children}
            </div>
        </div>
    )}
    />
)

See a mix of GraphQL there? Gatsby ships with GraphQL with which to query data. Remember that speed is key and GraphQL is known for speed. Gatsby 2.0 introduces the API StaticQuery to make data queries in static components like layouts. A similar API is PageQuery, which queries data solely in page components.

Recall that in layout.js, you imported the required modules, Link, StaticQuery, and graphql from gatsby. You also created a default export of a function that takes a children argument. With StaticQuery, you made a graphql query to fetch the site title from siteMetadata through the default query attribute.

Subsequently, the render attribute renders HTML elements with JSX. The queried data also comes into play here. Add the siteMetadata property to the gatsby-config.js file, as follows:

...
siteMetadata: {
        title: `Scotchy-Vid-Blog`,
},
...

With the above setup, your layout is consistent across all the pages, including the header, which is sourced from siteMetadata.

Next, style the layout component in layout.css with this snippet:

.nav{
    background: black;
    color: white;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: Arial, Helvetica, sans-serif;
    padding: 1% 0;
}
.child{
    width: 60%;
    margin: 0 auto;
    max-width: 1000px;
    margin-top: 2%;
}
h1{
    margin: 0;
    padding: 0;
}

You can now import the layout into any page.

Creation of Home Page

Similar to how you configured the layout component, edit the src/pages/index.js file to import the layout component and child elements, as follows:

//index.js
import React from "react"
import Layout from '../components/layout'
import './index.css'

export default ({data}) => {
    return (
        <Layout>
            <div>
                <header>
                    <h2>Let's place video memes here!</h2>
                </header>

            </div>
        </Layout>
    )
}

Create a CSS file index.css in the same directory to style the homepage.

body{
    margin: 0;
    padding: 0;
}
.post-item{
    margin-top: 5%;
}

.post-title, .post-date, .post-excerpt{
    margin: 10px;
}

.post-date{
    font-size: 0.8em;
    font-style: italic;
    color: darkolivegreen;
}

Restart the development server for a display of the home page:

Now that you’ve hammered together your basic home page with a working layout, create the video component. See the next section.

Creation of Video Component

A goal of this tutorial is to demonstrate how to use components in Markdown. So, create a simple react component for fetching videos that are stored in Cloudinary and then serve a video with the robust Cloudinary Video Player.

First, [create a free Cloudinary account](https://cloudinary.com/users/register/free). Second, in the Media Library tab, upload a few videos.

Note: In this tutorial, the values of the variables, such as cloud_name and publicID, are those for my Cloudinary account. Replace those values, including the public ID of any media asset you want to use, with the ones for your account, which are displayed on the Dashboard of your account.

Third, create a file called VideoPlayer.js in the src/components directory. This is a stateful component that fetches Cloudinary videos according to their public IDs. Import all the required modules by adding this snippet to VideoPlayer.js:

import React from "react";
import "lodash/lodash.js";
import cloudinary from "cloudinary-core";
import "cloudinary-video-player/dist/cld-video-player.js"
import "cloudinary-video-player/dist/cld-video-player.min.css"
import "jquery/dist/jquery.js"
import './videoplayer.css'

Next, create and export the component with this code:

[...]
class VideoPlayer extends React.Component {
    componentDidMount(){
        const {publicID, title, subtitle, muted} = this.props

        let cl = cloudinary.Cloudinary.new({cloud_name: "chuloo", secure: true})
        let videoPlayer = cl.videoPlayer('cl-vp',{
            loop: true,
            controls: true,
            muted: muted ? true : false,
        })
        videoPlayer.source(publicID ? publicID: "", {info:{title: title ? title : "", subtitle: subtitle ? subtitle : ""}})
    }

    render(){
        return (
            <div className="video-player">
                    <video 
                        autoPlay
                        id="cl-vp">
                    </video>
            </div>

        )
    }
}

export default VideoPlayer

In the componentDidMount lifecycle method, you created a Cloudinary instance with a cloud name and a video-player instance with the basic configurations to display the controls and to loop and mute the video, depending on the props received by the component.

Also, you assigned the video player a unique public ID (publicID), which is passed from props, also a title and subtitle is passed too. We then proceed to render the component in the render method before export.

In the same folder, create the imported css file videoplayer.css with:

.video-player{
    display: flex;
    justify-content: center;
    margin: 5% 0;
}

A complete VideoPlayercomponent is now available for use anywhere.

Creation of MDX Files

MDX enables the creation of React components in Markdown files with .mdx file formats. gatsby-mdx primarily helps you achieve that, and even processing normal .md Markdown files.

In src/posts create 4 MDX files: post01.mdx

---
title: "Simpson's Megaphone"
date: "2018-12-11"
---

// import VideoPlayer from "components/VideoPlayer"

## Spread Love! Supersonic Love!

Got the trick yet? Wait for it and i hope, you don't have headphones on!

{/_ <VideoPlayer
    publicID = "headphones"
    title = "Headphones"
    muted
/> _/}

Just so you know, this video is from Cloudinary!

post02.mdx

---
title: "Absolutely Nothing!"
date: "2018-12-10"
---

// import VideoPlayer from "components/VideoPlayer"

## What's for the season?

When I'm asked, "are you getting me anything this Christmas?"

{/_ <VideoPlayer
    publicID = "nothing"
    title = "Absolutely Nothing"
    subtitle = "Nothing ft. Santa"
    muted
/> _/}

Just so you know, this video is from Cloudinary!

post03.mdx

---
title: "Bedroom Magic!"
date: "2018-12-09"
---

// import VideoPlayer from "components/VideoPlayer"

## Trust the Process

From a chase through a refining process to an interrogation! 

{/_ <VideoPlayer
    publicID = "impossible"
    title = "Cut the chase"
    subtitle = "...or not"
/> _/}

Just so you know, this video is pulled from cloudinary!

welcome.mdx

---
title: "Poor"
date: "2018-08-10"
---

// import VideoPlayer from "components/VideoPlayer"

## Rude Machines!

Feeling broke or out of cash? Here's a sign that there's hope for you!

{/_ <VideoPlayer
    publicID = "weirdatm"
    title = "Rude ATM"
    subtitle = "Take ya card!!"
/> _/}

Just so you know, this video is from cloudinary!

Note that in the above MDX files, you commented out the import statements and components. Omitting that step would cause Gatsby to throw an error on gatsby develop because gatsby-mdx does not support relative imports. Later in this tutorial, you will set up a webpack configuration to specify the absolute path of each of the MDX files before importing them.

You’re now ready to create pages from the four MDX files.

Programmatic Creation of Pages

Creating pages programmatically is done in the gatsby-node.js file. Follow these steps:

Step 1: Create a file called gatsby-node.js in the root directory. To programmatically create pages from MDX content, you'll use two API exported by Gatsby, onCreateNode and createPages APIs. These are used to create node fields in the GraphQL schema and create pages from GraphQL queries of the MDX content.

Step 2: Create the route node field, which is the route for each of the pages. Import the required modules and create the path in gatsby-node.js file with this code:

const componentWithMDXScope = require("gatsby-mdx/component-with-mdx-scope");
const path = require("path")
const {createFilePath} = require("gatsby-source-filesystem")

exports.onCreateNode = ({node, getNode, actions}) =>{
    const {createNodeField} = actions
    if (node.internal.type === 'Mdx') {
        const route = createFilePath({node, getNode, basePath:'posts'})
        createNodeField({
            node,
            name: 'route',
            value: route
        })
    }
}

Page creation requires the scope of the MDX files, hence the import. Here, you also generated the route values from the individual file names with createFilePath from gatsby-source-filesystem.

Step 3: Restart your development server. Afterwards, open the GraphiQL interface and run a sample GraphQL query like the one below, to verify that the route fields are in place.

Perform step 4 to create pages and access them through their individual routes newly created.

Step 4: In gatsby-node.js , using createPages, fetch the scope, route, name, and sourceInstanceName using a GraphQL query.

exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions;
  return new Promise((resolve, reject) => {
    resolve(
      graphql(
        `{
            allMdx {
                edges {
                    node {
                        fields {
                            route
                        }
                        id
                        parent {
                            ... on File {
                                name
                                sourceInstanceName
                            }
                        }
                        code {
                            scope
                        }
                    }
                }
            }
          }`
      ).then(result => {
        if (result.errors) {
          console.log(result.errors);
          reject(result.errors);
        }

        result.data.allMdx.edges.forEach(({ node }) => {
          createPage({
            path: node.fields.route,
            component: componentWithMDXScope(
              path.resolve("./src/components/post-layout.js"),
              node.code.scope
            ),
            context: { 
                id: node.id, 
                postRoute: node.fields.route
            }
          });
        });
      })
    );
  });
};

Here, with the data queried, you’ve created pages for each of the MDX files, specifying the path as the route and the context object, whose properties are available as page variables. You’ve also specified the component for each of the pages.

Step 5: Create a file called post-layout.js in the src/components directory to serve as the component for each of the new pages. Populate post-layout.js with this content:

import React from "react"
import Layout from '../components/layout'
import { graphql} from "gatsby";
import MDXRenderer from 'gatsby-mdx/mdx-renderer'

export default ({data}) => {
    return (
        <Layout>
            <div>
                <header>
                    <h1 className="article-title">{data.mdx.frontmatter.title}</h1>
                </header>

                <div>
                  <MDXRenderer>{data.mdx.code.body}</MDXRenderer>
                </div>
            </div>
        </Layout>
    )
}

export const pageQuery = graphql`query ($postRoute: String!) {
        mdx(fields: {route: {eq: $postRoute}}){
                frontmatter{
                    title
                }
                fields{
                    route
                }
                rawBody
                internal{
                    content
                }
                code{
                    body
                    scope
                }
        }
    }`

Here, you rendered the content of the MDX filesby the means of MDXRenderer. Notice how you queried with pageQuery each of the required data points. Also, the page's route is determined from the context passed to the page.

Step 6: Restart your development server and, as a test, visit a wrong page, say, localhost:8000/mjnb. Besides the error message, also displayed are all the available pages, including those that you created earlier.

The building blocks for your video blog are now all set.

Configuration of Webpack

Because Gatsby-mdx does not support relative paths, you'll have to make a webpack config in gatsby-node.js. Also, the cld-video-player file contains the window object which throws an error during a build (Gatsby uses node.js during builds in which the window browser object is absent). In light of all that, call the onCreateWebpackConfig API in gatsby-node.js to get around the errors.

Add the following code to gatsby-node.js:

// gatsby-node.js
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
  actions.setWebpackConfig({
    resolve: {
      modules: [path.resolve(__dirname, 'src'), 'node_modules'],
    },
  })
  if(stage === "build-html"){
    actions.setWebpackConfig({
      module:{
        rules: [
          {
            test: /cld-video-player/,
            use: loaders.null()
          }
        ]
      }
    })
  }
}

The above snippet excludes the cld-video-player module during the build process and only runs in the browser. This same principle applies to all browser-dependent modules.

With the preceding setup, you can delete the comments in the MDX files for importing the videoplayer component.

Posting of Content on Home Page

Now that all the pages with video content are working, populate the individual links on the home page (the index.js file). By querying all the MDX files with GraphQL, you can obtain enough data for the home page. In the src/pages/index.js file, add a page query to the component:

export const query = graphql`query PageList{
        allMdx(sort: { fields: [frontmatter___date], order: DESC }){
            edges{
                node{
                    fields {
                        route
                    }
                    frontmatter{
                        title
                        date
                    }
                    timeToRead
                    excerpt
                }
            }
        }
    }`

With the data for all the MDX files in descending order, update the default export of the component to make use of the data:

[...]
export default ({data}) => {
    const posts = data.allMdx.edges
    return (
        <Layout>
            <div>
                <header>
                    <h2>Let's place video memes here!</h2>
                </header>
                <div>
                    {posts.map(({node}, index)=> (
                        <Link to={node.fields.route} style={{textDecoration:'none', color:  'inherit'}} key={index}>
                            <div className="post-item">
                                <h3 className="post-title">{node.frontmatter.title}</h3>
                                <p className="post-excerpt">{node.excerpt}</p>
                                <p className="post-date">{node.frontmatter.date}</p>
                            </div>
                        </Link>
                    ))}
                </div>
            </div>
        </Layout>
    )
}
[...]

Finally, restart your development server for a display of the the completed website.

Summary

You've now learned how to create video blogs having React components in Markdown files and also how to programmatically create pages from those files. The development opportunities with this process are limitless because you can make API calls within Markdown pages and make use of the data fetched on the DOM.

Run a build version of this tutorial. As reference, see the deployed version to Netlify and the repository.

Is this tutorial helpful to you? Do tell us your take in the Comments section below. We would also appreciate your sharing this post as you deem appropriate.

Nahoru
Tento web používá k poskytování služeb a analýze návštěvnosti soubory cookie. Používáním tohoto webu s tímto souhlasíte. Další informace