Shubho.dev logo
Gatsby

Generate RSS Feed from WordPress for your Gatsby Blog

I always start my day going over my RSS feeds. It has been an obsession for over 12 years now since I discovered it. So it’s no surprise that whenever I create any blog for myself or my friends and family, I make sure to set up an RSS feed for them.

My issues with Build times in Gatsby

While generating my Gatsby blog using WordPress as a CMS, I convert the posts to Markdown before reading it from the Gatsby GraphQL interface. Gatsby has an RSS plugin that can take your Markdown and create a feed out of it. But for some reason, my build time increased. Usually, you would generate them only during production. But I also love testing my feeds during dev. And just for the heck of it, I serve 3 types: RSS2, Atom and JSON. Why? Because I can. So I decided to write a local Gatsby plugin to generate the feeds myself.

Local Gatsby plugin to generate WordPress feeds

My initial idea was to read each Markdown post and then the XML for the feed manually. Which was really buggy? I found this excellent npm package, which can generate feeds. It properly escapes all the XML as required. The only input required is the HTML and the output paths. As a bonus, it can generate feeds for all the 3 types I need.

Anatomy of a Gatsby plugin

The first step in the process was figuring out how to create a gatsby plugin. The Gatsby plugin documentation explains the steps quite well. At a minimum, a Gatsby plugin needs 3 files:

  • A package.json file. - At a minimum, this file should have the name of the plugin. Since my plugin is a generic plugin following their suggested naming scheme, I named my plugin below.

    json
    {
      "name": "gatsby-plugin-wordpress-feed"
    }
  • An index.js file. I am still not sure about the usage of this file. But as per their notes, I have the minimum content here // noop 🙂

  • A gatsby-node.js file. - This is the meat of the plugin. It exports a lifecycle method for Gatsby. In my case it was onPostBuild().

The plugin content

The entire code is below. The comments should make it self explanatory. Also please note that this is specific to my site. You can and should update it as per your requirements.

js
// Require the packages.
const Feed = require('feed').Feed;
const fs = require('fs');
const moment = require('moment');

// This function creates the feed. It takes in the posts and the site metadata as input.
function createFeed(posts, siteData) {
    const feed = new Feed({
        title: siteData.title,
        description: siteData.description,
        link: siteData.url,
        id: siteData.url,
        copyright: createCopyRight(siteData.startYear, siteData.author),
        feedLinks: {
            atom: `${siteData.url}/atom.xml`,
            json: `${siteData.url}/feed.json`,
        },
        author: {
            name: siteData.author,
        }
    });

    posts.edges.forEach(({ node }) => {
        const feedItem = {
            title: node.title,
            id: `${siteData.url}/${node.categories[0].slug}/${node.slug}/`,
            link: `${siteData.url}/${node.categories[0].slug}/${node.slug}/`,
            date: moment(node.date).toDate(),
            content: node.childMarkdownWordpress.childMarkdownRemark.html,
            author: [
                {
                    name: siteData.author,
                    link: siteData.url
                }
            ]
        };
        // If the post contains a featured image, show it in the feed in the beginning.
        if (node.featured_media && node.featured_media.fields && node.featured_media.fields.Image_Url_1020x300) {
            feedItem.content = `<div style="margin: 0 auto; text-align: center;"><img src="${node.featured_media.fields.Image_Url_1020x300}" alt="Featured Image"></div>${node.childMarkdownWordpress.childMarkdownRemark.html}`;
        }
        feed.addItem(feedItem);
    });
    feed.addContributor({
        name: siteData.author,
        link: siteData.url
    });
    return feed;
}

function createDateString(startYear) {
    startYear = startYear.toString();
    const currentYear = new Date().getFullYear().toString();
    if (startYear !== currentYear) {
        return `${startYear} - ${currentYear}`;
    }
    return startYear;
}

function createCopyRight(startYear, author) {
    return `Copyright ${author} ${createDateString(startYear)}. License: Creative Commons Attribution-NonCommercial-ShareAlike https://creativecommons.org/licenses/by-nc-sa/4.0/`;
}

// The gatsby lifecycle onPostBuild is used. This is fired whenever posts are built during dev and production.
async function onPostBuild({
    graphql,
    reporter
}, {
    path = '/public/feed'
} = {}) {
    const rss = __dirname + '/../../' + path + '/index.xml';
    const atom = __dirname + '/../../' + 'public/atom.xml';
    const json = __dirname + '/../../' + 'public/feed.json';

    try {
        fs.mkdirSync(__dirname + '/../../' + path);
    } catch (e) {}

    return graphql(
        `
            {
                site {
                    siteMetadata {
                        title
                        description
                        url
                        startYear
                        author
                    }
                }
                blog: allWordpressPost(sort: {fields: date, order: DESC}, limit: 10) {
                    edges {
                        node {
                            date
                            fields {
                                createdAtFormatted
                            }
                            title
                            slug
                            categories {
                                name
                                slug
                            }
                            tags {
                                name
                            }
                            childMarkdownWordpress {
                                childMarkdownRemark {
                                    html
                                }
                            }
                            featured_media {
                                fields {
                                    Image_Url_1020x300
                                }
                            }
                        }
                    }
                }
            }
        `
    )
        .then((result) => {
            if (result.errors) {
                console.log("Error retrieving wordpress data", result.errors);
                throw result.errors;
            }
            const allPosts = result.data.blog;
            const siteMetaData = result.data.site.siteMetadata;
            // Create the feed data structure
            const feed = createFeed(allPosts, siteMetaData);

            // Generate the Atom feed
            const atomfeed = feed.atom1();
            fs.writeFileSync(atom, atomfeed, 'utf-8');

            // Generate the RSS feed
            const rssfeed = feed.rss2();
            fs.writeFileSync(rss, rssfeed, 'utf-8');

            // Generate the JSON feed
            const jsonfeed = feed.json1();
            fs.writeFileSync(json, jsonfeed, 'utf-8');
        })
        .catch((error) => {
            console.log(error);
            reporter.panicOnBuild(
                `Error generating feeds - ${error.message}`
            );
        });

}

exports.onPostBuild = onPostBuild;

As a final step, add the plugin to the gatsby-config.js file. Since our plugin has no parameters, we call it by its name gatsby-plugin-wordpress-feed in the plugins array.

Conclusion

Feeds are an integral part of your blog. Make sure you don’t miss these and be sure to test them. Provide multiple formats if possible. And finally, if a Gatsby plugin doesn’t provide your needs, try writing one for yourself. It’s fun.