Gatsby.JS Blog from markdown files and Adding Comments

[Fuente:

https://www.gatsbyjs.com/blog/2017-07-19-creating-a-blog-with-gatsby/#gatsby-skip-here

https://www.gatsbyjs.com/docs/adding-comments/]

Gatsby is an incredible static site generator that allows for React to be used as the underlying rendering engine to scaffold out a static site that truly has all the benefits expected in a modern web application. It does this by rendering dynamic React components into static HTML content via server side rendering at build time. This means that your users get all the benefits of a static site such as the ability to work without JavaScript, search engine friendliness, speedy load times, etc. without losing the dynamism and interactivity that is expected of the modern web. Once rendered to static HTML, client-side React/JavaScript can take over (if creating stateful components or logic in componentDidMount) and add dynamism to the statically generated content.

Gatsby recently released a v1.0.0 with a bunch of new features, including (but not limited to) the ability to create content queries with GraphQL, integration with various CMSs–including WordPress, Contentful, Drupal, etc., and route based code splitting to keep the end user experience as snappy as possible. In this post, we’ll take a deep dive into Gatsby and some of these new features by creating a static blog. Let’s get on it!

Adding necessary plugins

Gatsby supports a rich plugin interface, and many incredibly useful plugins have been authored to make accomplishing common tasks a breeze. Plugins can be broken up into three main categories: functional plugins, source plugins, and transformer plugins.

Functional plugins

Functional plugins either implement some functionality (e.g. offline support, generating a sitemap, etc.) or they extend Gatsby’s webpack configuration adding support for TypeScript, Sass, etc.

For this particular blog post, you will make a single page app-like feel (without page reloads) as well as the ability to dynamically change the title tag within the head tags. As noted, the Gatsby plugin ecosystem is rich, vibrant, and growing, so oftentimes a plugin already exists that solves the particular problem you’re trying to solve. To address the functionality you want for this blog, you can use the following plugins:

with the following command:

yarn add gatsby-plugin-catch-links gatsby-plugin-react-helmet

After installing each of these functional plugins, edit gatsby-config.js, which Gatsby loads at build-time to implement the exposed functionality of the specified plugins.

gatsby-config.js
module.exports = {  
  siteMetadata: {
    title: `Your Name - Blog`,
    author: `Your Name`
  },
  plugins: ["gatsby-plugin-catch-links", "gatsby-plugin-react-helmet"]
}

Without any additional work besides a yarn install and editing a config file, you now have the ability to edit your site’s head tags as well as implement a single page app feel without reloads. Now, let’s enhance the base functionality by implementing a source plugin which can load blog posts from your local file system.

Source plugins

Source plugins create nodes which can then be transformed into a usable format (if not already usable) by a transformer plugin. For instance, a typical workflow often involves using gatsby-source-filesystem, which loads files off of disk–e.g. Markdown files–and then specifying a Markdown transformer to transform the Markdown into HTML.

Since the bulk of the blog’s content and each article will be authored in Markdown, let’s add that gatsby-source-filesystem plugin. Similarly to the previous step, install the plugin and then inject into your gatsby-config.js, like so:

yarn add gatsby-source-filesystem

gatsby-config.js

module.exports = {
  // previous configuration
  plugins: [
    "gatsby-plugin-catch-links",
    "gatsby-plugin-react-helmet",
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/pages`,
        name: "pages"
      }
    }
  ]
}

Some explanation will be helpful here! An options object can be passed to a plugin, and you’re passing the filesystem path (which is where your Markdown files will be located) and then a name for the source files. Now that Gatsby knows about your source files, you can begin applying some useful transformers to convert those files into usable data!

Transformer plugins

As mentioned, a transformer plugin takes some underlying data format that is not inherently usable in its current form (e.g. Markdown, json, yaml, etc.) and transforms it into a format that Gatsby can understand and that you can query against with GraphQL. Jointly, the filesystem source plugin will load file nodes (as Markdown) off of your filesystem, and then the Markdown transformer will take over and convert to usable HTML.

You only need one transformer plugin (for Markdown), so let’s get that installed.

  • gatsby-transformer-remark
    • Uses the remark Markdown parser to transform .md files on disk into HTML; additionally, this transformer can optionally take plugins to further extend functionality–e.g. add syntax highlighting with gatsby-remark-prismjsgatsby-remark-copy-linked-files to copy relative files specified in markdown, gatsby-remark-images to compress images and add responsive images with srcset, etc.

The process should be familiar by now, install and then add to config.

yarn add gatsby-transformer-remark

gatsby-config.js

module.exports = {
  // previous setup
  plugins: [
    "gatsby-plugin-catch-links",
    "gatsby-plugin-react-helmet",
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        path: `${__dirname}/src/pages`,
        name: "pages"
      }
    },
    {
      resolve: "gatsby-transformer-remark",
      options: {
        plugins: [] // just in case those previously mentioned remark plugins sound cool :)
      }
    }
  ]
}

Whew! Seems like a lot of set up, but collectively these plugins are going to super charge Gatsby and give us an incredibly powerful (yet relatively simple!) development environment. There is one more setup step and it’s an easy one. You’re simply going to create a Markdown file that will contain the content of your first blog post. Let’s get to it.

Writing your first Markdown blog post

The gatsby-source-filesystem plugin you configured earlier expects your content to be in src/pages, so that’s exactly where it needs to be put in!

Gatsby is not at all prescriptive in naming conventions, but a typical practice for blog posts is to name the folder something like MM-DD-YYYY-title, e.g. 07-12-2017-hello-world. Let’s do just that. Create the folder src/pages/07-12-2017-getting-started and place an index.md inside!

The content of this Markdown file will be your blog post, authored in Markdown (of course!). Here’s what it’ll look like:

src/pages/07-12-2017-getting-started/index.md

---
path: "/hello-world"
date: 2017-07-12T17:12:33.962Z
title: "My First Gatsby Post"
---

Oooooh-weeee, my first blog post!

Fairly typical stuff, except for the block surrounded in dashes. What is that? That is what is referred to as frontmatter, and the contents of the block can be used to inject React components with the specified data, e.g. path, date, title, etc. Any piece of data can be injected here (e.g. tags, sub-title, draft, etc.), so feel free to experiment and find what necessary pieces of frontmatter are required to achieve an ideal blogging system for your usage. One important note is that path will be used when you dynamically create your pages to specify the URL/path to render the file (in a later step!). In this instancehttp://localhost:8000/hello-world will be the path to this file.

Now that you have created a blog post with frontmatter and some content, you can begin actually writing some React components that will display this data!

Creating the (React) template

As Gatsby supports server side rendering (to string) of React components, you can write your template in… you guessed it, React! (Or Preact, if that’s more your style)

You should create the file src/templates/blog-post.js (please create the src/templates folder if it does not yet exist!).

src/templates/blog-post.js

import React from "react"
import { Helmet } from "react-helmet"

// import '../css/blog-post.css'; // make it pretty!

export default function Template({
  data // this prop will be injected by the GraphQL query we'll write in a bit
}) {
  const { markdownRemark: post } = data // data.markdownRemark holds your post data
  return (
    <div className="blog-post-container">
      <Helmet title={`Your Blog Name - ${post.frontmatter.title}`} />
      <div className="blog-post">
        <h1>{post.frontmatter.title}</h1>
        <div
          className="blog-post-content"
          dangerouslySetInnerHTML={{ __html: post.html }}
        />
      </div>
    </div>
  )
}

Whoa, neat! This React component will be rendered to a static HTML string (for each route/blog post you define), which will serve as the basis of your routing/navigation for your blog.

At this point, there is a reasonable level of confusion and “magic” occurring, particularly with the props injection. What is markdownRemark? Where is this data prop injected from? All good questions, so let’s answer them by writing a GraphQL query to seed your <Template /> component with content!

Writing the GraphQL query

Below the Template declaration, you’ll want to add a GraphQL query. This is an incredibly powerful utility provided by Gatsby which lets us pick and choose very simply the pieces of data that you want to display for your blog post. Each piece of data your query selects will be injected via the data property that you specified earlier.

src/templates/blog-post.js

import React from "react"
import { Helmet } from "react-helmet"
import { graphql } from "gatsby"

// import '../css/blog-post.css';

export default function Template({ data }) {
  const { markdownRemark: post } = data
  return (
    <div className="blog-post-container">
      <Helmet title={`Your Blog Name - ${post.frontmatter.title}`} />
      <div className="blog-post">
        <h1>{post.frontmatter.title}</h1>
        <div
          className="blog-post-content"
          dangerouslySetInnerHTML={{ __html: post.html }}
        />
      </div>
    </div>
  )
}

export const pageQuery = graphql`
  query BlogPostByPath($path: String!) {
    markdownRemark(frontmatter: { path: { eq: $path } }) {
      html
      frontmatter {
        date(formatString: "MMMM DD, YYYY")
        path
        title
      }
    }
  }
`

If you’re not familiar with GraphQL, this may seem slightly confusing, but you can break down what’s going down here piece by piece.

Note: To learn more about GraphQL, consider this excellent resource

The underlying query name BlogPostByPath (note: these query names need to be unique!) will be injected with the current path, e.g. the specific blog post we are viewing. This path will be available as $path in your query. For instance, if you were viewing your previously created blog post, the path of the file that data will be pulled from will be /hello-world.

markdownRemark will be the injected property available via the prop data, as named in the GraphQL query. Each property you pull via the GraphQL query will be available under this markdownRemark property. For example, to access the transformed HTML, you would access the data prop via data.markdownRemark.html.

frontmatter, is of course the data structure we provided at the beginning of the Markdown file. Each key you define there will be available to be injected into the query.

At this point, you have a bunch of plugins installed to load files off of disk, transform Markdown to HTML, and other utilities. You have a single, lonely Markdown file that will be rendered as a blog post. Finally, you have a React template for blog posts as well as a wired up GraphQL query to query for a blog post and inject the React template with the queried data. Next up: programmatically creating the necessary static pages (and injecting the templates) with Gatsby’s Node API. Let’s get down to it.

An important note to make at this point is that the GraphQL query takes place at build time. The component is injected with the data prop that is seeded by the GraphQL query. Unless anything dynamic (e.g. logic in componentDidMount, state changes, etc.) occurs, this component will be pure, rendered HTML generated via the React rendering engine, GraphQL, and Gatsby!

Creating the static pages

Gatsby exposes a powerful Node API, which allows for functionality such as creating dynamic pages (blog posts!), extending the babel or webpack configs, modifying the created nodes or pages, etc. This API is exposed in the gatsby-node.js file in the root directory of your project—e.g. at the same level as gatsby-config.js. Each export found in this file will be parsed by Gatsby, as detailed in its Node API specification. However, you only need to care about one particular API in this instance, createPages.

gatsby-node.js

const path = require("path")

exports.createPages = async ({ actions, graphql }) => {
  const { createPage } = actions

  const blogPostTemplate = path.resolve(`src/templates/blog-post.js`)
}

Nothing super complex yet! You’re using the createPages API (which Gatsby will call at build time with injected parameters). You’re also grabbing the path to your blogPostTemplate that you created earlier. Finally, you’re using the createPage action creator/function made available in actions. Gatsby uses Redux internally to manage its state, and actions are simply the exposed action creators of Gatsby, of which createPage is one of the action creators! For the full list of exposed action creators, check out Gatsby’s documentation. You can now construct the GraphQL query, which will fetch all of your Markdown posts.

Querying for posts

gatsby-node.js

const path = require("path")

exports.createPages = async ({ actions, graphql, reporter }) => {
  const { createPage } = actions

  const blogPostTemplate = path.resolve(`src/templates/blog-post.js`)

  const result = await graphql(`
    {
      allMarkdownRemark(
        sort: { order: DESC, fields: [frontmatter___date] }
        limit: 1000
      ) {
        edges {
          node {
            frontmatter {
              path
            }
          }
        }
      }
    }
  `)

  if (result.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`)
    return
  }
}

You’re using GraphQL to get all Markdown nodes and making them available under the allMarkdownRemark GraphQL property. Each exposed property (on node) is made available for querying against. You’re effectively seeding a GraphQL “database” that you can then query against via page-level GraphQL queries. One note here is that the exports.createPages API expects a Promise to be returned, so it works seamlessly with the graphql function, which returns a Promise (although note a callback API is also available if that’s more your thing).

One cool note here is that the gatsby-plugin-remark plugin exposes some useful data for us to query with GraphQL, e.g. excerpt (a short snippet to display as a preview), id (a unique identifier for each post), etc.

You now have your query written, but are yet programmatically created the pages (with the createPage action creator). Let’s do that!

Creating the pages

gatsby-node.js

const path = require("path")

exports.createPages = async ({ actions, graphql, reporter }) => {
  const { createPage } = actions

  const blogPostTemplate = path.resolve(`src/templates/blog-post.js`)

  const result = await graphql(`
    {
      allMarkdownRemark(
        sort: { order: DESC, fields: [frontmatter___date] }
        limit: 1000
      ) {
        edges {
          node {
            frontmatter {
              path
            }
          }
        }
      }
    }
  `)

  if (result.errors) {
    reporter.panicOnBuild(`Error while running GraphQL query.`)
    return
  }

  result.data.allMarkdownRemark.edges.forEach(({ node }) => {
    createPage({
      path: node.frontmatter.path,
      component: blogPostTemplate,
      context: {} // additional data can be passed via context
    })
  })
}

You’ve now tied into the Promise chain exposed by the graphql query. The actual posts are available via the path result.data.allMarkdownRemark.edges. Each edge contains an internal node, and this node holds the useful data that you will use to construct a page with Gatsby. Your GraphQL “shape” is directly reflected in this data object, so each property you pulled from that query will be available when you are querying in your GraphQL blog post template.

The createPage API accepts an object which requires path and component properties to be defined, which you have done above. Additionally, an optional property context can be used to inject data and make it available to the blog post template component via injected props (log out props to see each available prop!). Each time you build with Gatsby, createPage will be called, and Gatsby will create a static HTML file of the path you specified in the post’s frontmatter–the result of which will be your stringified and parsed React template injected with the data from your GraphQL query. Whoa, it’s actually starting to come together!

You can run yarn develop at this point and then navigate to http://localhost:8000/hello-world to see your first blog post, which should look something like below:

My first blog post rendered

At this point, you’ve created a single static blog post as an HTML file, which was created by a React component and several GraphQL queries. However, this isn’t a blog! You can’t expect your users to guess the path of each post, you need to have an index or listing page, where you display each blog post, a short snippet, and a link to the full blog post. Wouldn’t you know it, this can be done incredibly easily with Gatsby, using a similar strategy that was used in the blog template, i.e. a React component and a GraphQL query.

Creating the Blog Listing

I won’t go into quite as much detail for this section because we’ve already done something very similar for our blog template! Look at us, we’re pro Gatsby-ers at this point!

Gatsby has a standard for “listing pages,” and they’re placed in the root of our filesystem we specified in gatsby-source-filesystem, e.g. src/pages/index.js. So, create that file if it does not exist, and let’s get it working! Additionally, note that any static JavaScript files (that export a React component!) will get a corresponding static HTML file. For instance, if we create src/pages/tags.js, the path http://localhost:8000/tags/ will be available within the browser and the statically generated site.

src/pages/index.js

import React from "react"
import { Link, graphql } from "gatsby"
import { Helmet } from "react-helmet"

// import '../css/index.css'; // add some style if you want!

export default function Index({ data }) {
  const { edges: posts } = data.allMarkdownRemark
  return (
    <div className="blog-posts">
      {posts
        .filter(post => post.node.frontmatter.title.length > 0)
        .map(({ node: post }) => {
          return (
            <div className="blog-post-preview" key={post.id}>
              <h1>
                <Link to={post.frontmatter.path}>{post.frontmatter.title}</Link>
              </h1>
              <h2>{post.frontmatter.date}</h2>
              <p>{post.excerpt}</p>
            </div>
          )
        })}
    </div>
  )
}

export const pageQuery = graphql`
  query IndexQuery {
    allMarkdownRemark(sort: { order: DESC, fields: [frontmatter___date] }) {
      edges {
        node {
          excerpt(pruneLength: 250)
          id
          frontmatter {
            title
            date(formatString: "MMMM DD, YYYY")
            path
          }
        }
      }
    }
  }
`

OK! So we’ve followed a similar approach to your blog post template, so this should hopefully seem pretty familiar. Once more we’re exporting pageQuery which contains a GraphQL query. Note that we’re pulling a slightly different data set — specifically, we are pulling an excerpt of 250 characters rather than the full HTML as we are formatting the pulled date with a format string! GraphQL is awesome.

The actual React component is fairly trivial, but one important note should be made. It’s important that when linking to internal content, e.g. other blog links, that you should always use Link from gatsby. Gatsby does not work if pages are not routed via this utility. Additionally, this utility also works with pathPrefix, which allows for a Gatsby site to be deployed on a non-root domain. This is useful if this blog will be hosted on something like GitHub Pages or perhaps hosted at /blog.

Now, this is getting exciting and it feels like we’re finally getting somewhere! At this point, we have a fully functional blog generated by Gatsby, with real content authored in Markdown, a blog listing, and the ability to navigate around in the blog. If you run yarn develophttp://localhost:8000 should display a preview of each blog post, and each post title links to the content of the blog post. A real blog!

 

Creating Pages from Data Programmatically

Gatsby and its ecosystem of plugins provide all kinds of data through a GraphQL interface. This guide will show how that data can be used to programmatically create pages.

Prerequisites

Though you can use any data source you’d like, this guide will show how to create pages from Markdown files (following after the example introduced in earlier guides).

Creating pages

The Gatsby Node API provides the createPages extension point which you’ll use to add pages. This function will give you access to the createPage action which is at the core of programmatically creating a page.

gatsby-node.js

exports.createPages = async function ({ actions, graphql }) {
  const { data } = await graphql(`
    query {
      allMarkdownRemark {
        edges {
          node {
            fields {
              slug
            }
          }
        }
      }
    }
  `)
  data.allMarkdownRemark.edges.forEach(edge => {
    const slug = edge.node.fields.slug
    actions.createPage({
      path: slug,
      component: require.resolve(`./src/templates/blog-post.js`),
      context: { slug: slug },
    })
  })
}

For each page you want to create you must specify the path for visiting that page, the component template used to render that page, and any context you need in the component for rendering.

The context parameter is optional, though often times it will include a unique identifier that can be used to query for associated data that will be rendered to the page. All context values are made available to a template’s GraphQL queries as arguments prefaced with $, so from our example above the slug property will become the $slug argument in our page query:

JS

export const query = graphql`
  query($slug: String!) {
    ...
  }
`

Specifying a template

The createPage action requires that you specify the component template that will be used to render the page. Here is an example of what the referenced template could look like:

blog-post.js
import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"

export default function BlogPost({ data }) {
  const post = data.markdownRemark
  return (
    <Layout>
      <div>
        <h1>{post.frontmatter.title}</h1>
        <div dangerouslySetInnerHTML={{ __html: post.html }} />
      </div>
    </Layout>
  )
}

export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
      }
    }
  }
`

Notice that you’re able to query with the $slug value from your context as an argument, which ensures that you’re returning only the data that matches that specific page. As a result, you can provide the title and html from the matching markdownRemark record to your component. The context values are also available as the pageContext prop in the template component itself.

Not just Markdown

The gatsby-transformer-remark plugin is just one of a multitude of Gatsby plugins that can provide data through the GraphQL interface. Any of that data can be used to programmatically create pages.

Other resources

Adding Comments

If you’re using Gatsby to run a blog and you’ve started adding some content to it, the next thing to think about is how to increase engagement among your visitors. A great way to do that is to allow them to ask questions and express their views on what you’ve written. This will make your blog seem much more lively to anyone visiting it.

There are many options out there for adding comment functionality, several of them specifically targeted at static sites. While this list is by no means exhaustive, it does serve as a good starting point to illustrate what’s available:

  • Disqus
  • Commento
  • Facebook Comments
    Fast Comments
  • Staticman
  • TalkYard
  • Gitalk
  • Utterances
    You can also roll your own comment system, as Tania Rascia wrote on the Gatsby blog.

Using Disqus for comments

In this guide, you’ll learn how to implement Disqus on your blog as it has a number of nice features.

  • It is low maintenance, meaning moderating your comments and maintaining your forum less hassle.
  • It provides official React support.
  • It offers a generous free tier.
  • It seems to be by far the most widely used service.
  • It’s easier to comment: Disqus has a large existing user base and the onboarding experience for new users is fast. You can register with your Google, Facebook or Twitter account and users can more seamlessly share the comments they write through those channels.

The Disqus UI has a distinct but unobtrusive look that many users will recognize and trust.

All Disqus components are lazy-loaded, meaning they won’t negatively impact the load time of your posts.

Bear in mind, however, that choosing Disqus also incurs a tradeoff. Your site is no longer entirely static but depends on an external platform to deliver your comments through embedded iframes on the fly.

Moreover, you should consider the privacy implications of letting a third party store your visitors’ comments and potentially track their browsing behavior. You may consult the Disqus privacy policy, the privacy FAQs (specifically the last question on GDPR compliance) and inform your users how to edit their data sharing settings.

Implementing Disqus

Here are the steps for adding Disqus comments to your own blog:

  1. Sign-up to Disqus. During the process you’ll have to choose a shortname for your site. This is how Disqus will identify comments coming from your site. Copy that for later.
  2. Install the Disqus React package
npm install disqus-react

3. Add the shortname from step 1 as something like GATSBY_DISQUS_NAME to your .env and .env.example files so that people forking your repo will know that they need to supply this value to get comments to work. (You need to prefix the environment variable with GATSBY_ in order to make it available to client-side code.)

.env.example

# enables Disqus comments for blog posts
GATSBY_DISQUS_NAME=insertValue

.env

GATSBY_DISQUS_NAME=yourSiteShortname

4. In your blog post template (usually src/templates/post.js) import the DiscussionEmbed component.
src/templates/post.js

import React from "react"
import { graphql } from "gatsby"
import { DiscussionEmbed } from "disqus-react"

Then define your Disqus configuration object

Copycopy code to clipboard
const disqusConfig = {
shortname: process.env.GATSBY_DISQUS_NAME,
config: { identifier: slug, title },
}
Where identifier must be a string or number that uniquely identifies the post. It could be the post’s slug, title or some ID. Finally, add DiscussionEmbed to the JSX of your post template.

src/templates/post.js
Copysrc/templates/post.js: copy code to clipboard
return (
<Global>
<PageBody>
<DiscussionEmbed {…disqusConfig} />
</PageBody>
</Global>
)
And you’re done. You should now see the Disqus comment form appear beneath your blog post looking like this. Happy blogging!

Disqus comments