Building a Video Blog with Gatsby and Markdown (MDX)

Building a Video Blog with Gatsby and Markdown (MDX)

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..

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..

Table of Contents

  • What You'll Build
  • Benefits of Gatsby.js, MDX, and Cloudinary
  • Prerequisites
  • Installation
  • Creation of Gatsby Site
  • Creation of Directories
  • Configuration of Plugins
  • Creation of Layout
  • Creation of Home Page
  • Creation of Video Component
  • Creation of MDX Files
  • Programmatic Creation of Pages
  • Configuration of Webpack
  • Posting of Content on Home Page
  • Summary

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.


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


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

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:

Free Node eBook

Build your first Node apps and learn server-side JavaScript.

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.


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>{}</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](\). 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 ={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!


--- 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!


--- 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!


--- 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); }{ node }) => { createPage({ path: node.fields.route, component: componentWithMDXScope( path.resolve("./src/components/post-layout.js"), node.code.scope ), context: { 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> {{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">{}</p> </div> </Link> ))} </div> </div> </Layout> ) } [...]

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


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.

javascript programming

What's new in Bootstrap 5 and when Bootstrap 5 release date?

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Random Password Generator Online

HTML Color Picker online | HEX Color Picker | RGB Color Picker

How to start writing less error-prone code in JavaScript

How to start writing less error-prone code in JavaScript - Everything in JavaScript is an object!’. We said that this assertion is false. Many things in JavaScript can behave like an object, but that doesn’t mean it’s the object. We can say we have three types of objects (objects, functions and arrays) in JavaScript.

The essential JavaScript concepts that you should understand

The essential JavaScript concepts that you should understand - For successful developing and to pass a work interview

Taking functional programming to JavaScript objects

Functional programming seems to be where the hype is nowadays — And for good reason; It can drastically reduce the amount of boilerplate code that you have to write in comparison to imperative programming which allows you to improve the readability of your code and consequently, maintainability.

9 JavaScript Tricks making programming life much easier

Making my programming life much easier and they look pretty cool in the editor. 9 JavaScript Tricks I Live By. Destructuring, Dynamic Property Names, map(), filter(), Template Literals, Short Circuit Conditionals, The Spread Operator Spreads values from one (or more) array(s) into another to form a single array, some(), Returns true if at least one of the elements in your array satisfies the condition you’re passing in. False otherwise, includes().

Functional Programming in JavaScript

Let’s explore the JavaScript features that make functional programming possible, discuss different functional programming concepts, and see why they are so powerful.