Shubho.dev logo

How I created my GatsbyJS blog with WordPress as the backend – Part 3

Now that we have set up the WordPress blog, I will now discuss the Gatsby setup.

** This blog uses Gatsby version 2.9.4. Gatsby has since released version 3 with lots of updates.**

Gatsby Config file

Gatsby configuration, including plugin details, go in gatsby-config.js file. Here is how I have structured the file.

js
if (process.env.NODE_ENV === 'development') {
    require("dotenv").config({
        path: `.env.development`,
    });
}
if (!process.env.SOURCE_WP_URL || process.env.SOURCE_WP_URL === '' ||
    !process.env.DEST_WP_URL || process.env.DEST_WP_URL === '') {
    throw new Error('WP URLs are required');
}

module.exports = {
    siteMetadata: {
        title: process.env.SITE_TITLE,
        description: process.env.SITE_DESCRIPTION,
        author: process.env.AUTHOR,
        email: process.env.EMAIL || '',
        url: process.env.SITE_URL,
        siteUrl: process.env.SITE_URL,
        pic: process.env.MAIN_PIC,
        startYear: process.env.START_YEAR,
        introTagLines: process.env.INTRO_TAG_LINES.split('-_-')
    },
    plugins: [
        {
            resolve: "gatsby-plugin-social-svg",
            options: {
                configFile: 'social.json',
                iconsDir: 'images/social'
            },
        },
        `gatsby-plugin-react-helmet`,
        {
            resolve: `gatsby-plugin-react-helmet-canonical-urls`,
            options: {
                siteUrl: `https://www.shubho.dev`,
            },
        },
        `gatsby-plugin-catch-links`,
        {
            resolve: `gatsby-source-filesystem`,
            options: {
                name: `images`,
                path: `${__dirname}/src/images`,
            },
        },
        `gatsby-transformer-sharp`,
        `gatsby-plugin-sharp`,
        `gatsby-plugin-postcss`,
        {
            resolve: `gatsby-transformer-remark`,
            options: {
                plugins: [
                    {
                        resolve: `gatsby-remark-reading-time`
                    },
                    {
                        resolve: `gatsby-remark-embed-gist`,
                    },
                    {
                        resolve: `gatsby-remark-prismjs`,
                        options: {
                            classPrefix: "language-",
                            aliases: {
                                javascript: 'js'
                            },
                            inlineCodeMarker: '>>',
                            showLineNumbers: false,
                            noInlineHighlight: false,
                            showLanguage: true
                        }
                    }
                ]
            }
        },
        `gatsby-plugin-favicon`,
        {
            resolve: "gatsby-source-wordpress",
            options: {
                baseUrl: process.env.BASE_WP_URL,
                protocol: process.env.BASE_PROTOCOL,
                hostingWPCOM: false,
                useACF: true,
                acfOptionPageIds: [],
                auth: {
                    htaccess_user: process.env.REST_UN,
                    htaccess_pass: process.env.REST_PWD,
                    htaccess_sendImmediately: true,
                },
                cookies: {},
                verboseOutput: false,
                perPage: 100,
                searchAndReplaceContentUrls: {
                    sourceUrl: process.env.SOURCE_WP_URL,
                    replacementUrl: process.env.DEST_WP_URL,
                },
                concurrentRequests: 10,
                includedRoutes: [
                    "**/categories",
                    "**/posts",
                    "**/pages",
                    "**/media",
                    "**/tags",
                    "**/taxonomies",
                    "**/users",
                ],
                excludedRoutes: [],
                normalizer: function({ entities }) {
                    return entities
                },
            }
        },
        {
            resolve: `gatsby-transformer-wordpress-markdown`,
            options: {
                turndownPlugins: ['turndown-plugin-gfm']
            }
        },
        `gatsby-plugin-wordpress-feed`,
        {
            resolve: `gatsby-plugin-advanced-sitemap`,
            options: {
                exclude: [
                    `/dev-404-page`,
                    `/404`,
                    `/404.html`,
                    `/offline-plugin-app-shell-fallback`,
                    `/blog/*`
                ],
                createLinkInHead: true
            }
        }
    ],
};

Most of the data are specified using environment variables. I use a bunch of official Gatsby plugins. The noteworthy ones for my setup are:

  1. gatsby-source-wordpress - This retrieves the data from my backend WordPress setup and converts it to the GraphQL data structure ready for consumption by the Gatsby build process. Since my backend CMS and the frontend blog URLs are different, I search and replace the CMS URL with my blog URL during this process.
  2. gatsby-plugin-react-helmet - This updates the metadata and links in the <head> section of all my pages.
  3. Image processing plugins - gatsby-source-filesystem includes the images on my build. gatsby-transformer-sharp and gatsby-plugin-sharp processes these.

There are couple more plugins for generating a sitemap, canonical URLs, processing the CSS using PostCSS etc.

In addition to these, I have written three plugins. These are gatsby-plugin-social-svg, gatsby-transformer-wordpress-markdown and gatsby-plugin-wordpress-feed.

gatsby-transformer-wordpress-markdown is used to convert the WordPress post content’s HTML back to Markdown. The remark plugins from Gatsby processes this Markdown and makes it available using GraphQL. I have written more about this in this post.

gatsby-plugin-social-svg generates the social links that you see on my blog’s homepage. The social link data is read from a JSON file.

gatsby-plugin-wordpress-feed creates the RSS, Atom and JSON feed for this blog. I have written more about this in this post.

Gatsby Node file

During the build process, the gatsby-node.js file is responsible for all the processing of the WordPress post. My file looks as follows (shortened portions for brevity):

js
const path = require(`path`);
const slash = require(`slash`);
const moment = require(`moment-timezone`);

exports.onCreateNode = ({ node, actions, getNode }) => {
    const { createNodeField } = actions;
    if (node.internal.type === "wordpress__POST" || node.internal.type === "wordpress__PAGE") {
        createNodeField({
            name: "createdAtFormatted",
            value: moment(node.date).tz('Universal').tz('Asia/Calcutta').format(`MMMM DD, YYYY`),
            node,
        });
        ...
    }
    if (node.internal.type === "wordpress__wp_media") {
        const url = node.source_url;
        const photoslug = node.slug;
        const ext = url.split('.').slice(-1)[0];
        if (['jpeg', 'jpg', 'png', 'gif'].indexOf(ext) > -1) {
            const rootpath = url.split('/').slice(0, -1).join('/');
            createNodeField({
                name: "Image_MainUrl",
                value: url,
                node,
            });
            ...
        }
    }
};

exports.createPages = ({ graphql, actions }) => {
    const { createPage } = actions;
    return graphql(
        `
            {
                blog: allWordpressPost {
                    edges {
                        node {
                            id
                            slug
                            categories {
                                name
                            }
                        }
                    }
                },
                page: allWordpressPage {
                    edges {
                        node {
                            id
                            slug
                        }
                    }
                },
                categories: allWordpressCategory {
                    edges {
                        node {
                            id
                            name
                            slug
                            count
                        }
                    }
                },
                tags: allWordpressTag {
                    edges {
                        node {
                            id
                            name
                            slug
                            count
                        }
                    }
                }
            }
        `
    ).then(result => {
        if (result.errors) {
            console.log("Error retrieving wordpress data", result.errors);
        }
        const blogPostTemplate = path.resolve("./src/templates/blogpost.js");
        result.data.blog.edges.forEach(edge => {
            createPage({
                path: `/${edge.node.categories[0].name.toLowerCase()}/${edge.node.slug}/`,
                component: slash(blogPostTemplate),
                context: {
                    slug: edge.node.slug,
                    id: edge.node.id
                }
            });
        });
        const categoryTemplate = path.resolve("./src/templates/categorylist.js");
        result.data.categories.edges.forEach(({ node }) => {
            if (node.count > 0) {
                createPage({
                    path: `/${node.slug.toLowerCase()}`,
                    component: slash(categoryTemplate),
                    context: {
                        slug: node.slug,
                        name: node.name,
                        id: node.id,
                        taxonomy: 'category'
                    }
                });
            }
        });
        ...
    })
        .catch(error => {
            console.log("Error retrieving contentful data", error);
        });
};

I am retrieving the WordPress posts, pages, categories and tags data using GraphQL. I am creating a couple of extra fields during this process using the onCreateNode() API. Once created, these are available to the createPage() API.

There are specific templates for posts/pages and category/tag pages. The createPage() API creates these pages.

Gatsby Browser file

I am importing all the global styles in gatsby-browser file. These include the prismjs styles for code highlighting and Github gist styles.

Conclusion

In this part, I covered the setup files for Gatsby. The plugin ecosystem is enormous. You can write plugins yourself if you have any specific requirements. In the concluding part, I will quickly go through the theme setup and the React components.