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 wasonPostBuild()
.
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.
// 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.