DEV Community

Jeremy Wynn
Jeremy Wynn

Posted on • Originally published at jeremywynn.com on

Creating a Feed for a Git-based CMS with Nuxt

Since I'm currently using Netlify CMS for my blog, I wanted to create a RSS feed for it even if their usage may be statistically down. The site is statically generated using Nuxt, so in doing further research on anything out there that creates a feed, I found Feed module - Everyone deserves RSS, Atom and Json.

I've already followed the guide on setting up Netlify CMS with Nuxt, so this guide will be geared slightly toward that setup and configuration. Netlify CMS is configured to save blog posts in /assets/content/blog/ in .json files. However, the example given in the Configuration of the Feed create function of the feed-module is based on a JSON collection of posts from an API. This data for the feed create function can be easily be retrieved using an API set up to deliver it (e.g. Contentful API), but how do you get this data from a directory of files created by a Git-based CMS?

Getting all posts based off of files in a directory

This can be done some work in nuxt.config.js which will be used when the generate command is used which will ultimately create the feed.xml file. In this file, make sure you include these three dependencies that will be needed:

const fs = require('fs'); 
const glob = require('glob'); 
const path = require('path'); 

glob will search for and match the blog posts files, path will help resolve the paths and related references, and fs will allow these files to be parsed so that their data can be used to create the feed. Here is the code in nuxt.config.js that I used:

let dynamicRoutes = []; 
let globSearchResults = glob.sync('*.json', { cwd: 'assets/content/blog' }); 
let posts = []; 

globSearchResults.forEach(file => { 
  fs.readFile(path.resolve('assets/content/blog', file), 'utf8', function(err, data) { 
    if (err) { 
      console.log(err); 
    } else { 
      let post = JSON.parse(data); 
      post.url = 'https://jeremywynn.com/blog/' + path.parse(file).name;
      posts.push(post); 
    } 
  }); 
  let dynamicRoute = '/blog/' + path.parse(file).name;
  dynamicRoutes.push(dynamicRoute); 
}); 

Each .json file has 4 properties: title, date, description, and body. When each file is found by glob, fs will parse its contents (using JSON.parse()) where it will be assigned to a variable that is pushed to the posts array which is set up to collect all the posts.

Note: The dynamicRoutes bit is where I refactored the code under the Generating pages with the generate property section of the Netlify CMS for Nuxt Doc. Since the globbing is already being done above, entries for dynamicRoutes can be made and pushed here, and then the generate method in Nuxt can be reduced to this:

generate: { 
  routes: dynamicRoutes 
}, 

Giving data and configuring the Nuxt Feed module

Now the data in the posts array is ready for the feed create function portion for the feed part in nuxt.config.js:

feed: [
  { 
    path: '/feed.xml', 
    async create(feed, data) { 
      feed.options = { 
        title: 'Title goes here', 
        link: 'URL goes here', 
        description: 'Description goes here' 
      } 
      data.forEach(post => { 
        feed.addItem({ 
          title: post.title, 
          id: post.url, 
          link: post.url, 
          date: new Date(post.date), 
          description: post.description, 
          content: post.body 
        }) 
      }) 
    }, 
    cacheTime: 1000 * 60 * 15, 
    type: 'rss2', 
    data: posts 
  }
], 

The data property is assigned to the posts array that was populated earlier. Since post.date is merely a string representing a date, it will need to be converted to a Date object, or the feed module will throw an error.

Don't forget to include @nuxtjs/feed in the array of modules in nuxt.config.js:

modules: [
  '@nuxtjs/feed', 
  '@nuxtjs/robots'
], 

If you wish to place a link to this feed in your head, add this to your link array in nuxt.config.js:

{ rel: "alternate", type: "application/rss+xml", title: "Your Title", href: "https://yoursite.com/yourfeed.xyz" } 

In the end, you'll end up with a feed like this. This file will only be created when nuxt generate is run. When hosting on a service like Netlify, you can set this command to automatically run each and every time you create a blog post in a Git-based CMS since it updates the Git repository that comprises your site.

Top comments (5)

Collapse
 
kp profile image
KP • Edited

@jeremywynn I am using Nuxt in Universal / SSR (with Laravel APIs) and have a dynamic feed. The feed needs to be infinitely loaded, like on dev.to's homepage. Is the nuxt feed module going to work in this case? A working example would be helpful

Collapse
 
jeremywynn profile image
Jeremy Wynn

No, the feed module is for generating something like dev.to/feed which is a more computer-readable format that news readers/aggregators digest. You'll need something different for infinitely loading behavior of posts like you see on dev.to/

This will be code that will most likely live inside the page component file you are looking to incorporate this behavior for, like index.vue.

I've done something like this, but in SPA mode. I used IntersectionObserver (it's a Web API) in an element at the bottom of the page, and then have that trigger an event to load more posts after the last post.

However you are sorting your posts when you are displaying the first set on the page, you'll need to use that method to get the next set of posts from your API or whatnot.

In my case, I was using MongoDB which allows sorting based on id. In essence, the IntersectionObserver listened to whenever I scrolled to the bottom of my list, retrieved the id of the last post that was rendered on the page, then I queried for the next set of posts to load:

pipeline = [{ $match: { _id: { $lt: oid }}}, { $sort: { "_id": -1 } }, { $limit: 20 }];

Don't worry about the specifics of that code, but it is part of asking MongoDB to get the next 20 posts that are less than the id of the oid I passed it, which I retrieved from looking at the last post after IntersectionObserver was triggered.

So, you will need to find out how to "step through" the posts you are retrieving (e.g. with Laravel APIs), and use some sorting identifier of the last post as a cursor to use API to get the next batch of posts.

When you add this data to a data property, state, or whatever you have Vue watching, your component should be updated automatically. When you scroll down to the new bottom, the IntersectionObserver event should get the last post you have rendering, and then you'll use the API to get your next set of posts that should go after the last one.

Hope that helps.

Collapse
 
kp profile image
KP

@jeremywynn Thank you, this is super helpful and good to know since it sounds like you've done it before. Alex Lichter on the nuxt channel told me that the feed module could be used for dynamic SSR, but that didn't sound right to me, and no matter what I tried, I couldnt get it to work.
I previously did have the infinite loading working but not in SSR mode (I was not using the IntersectionObserver, so viewing the source of the page showed NOTHING - yes, terrible for SEO).

What you described (IntersectionObserver) is exactly the path I am on right now...it was recommended by a person on Discord....but I have not managed to get it to work and would really appreciate your eyes on this. Mind if I send you a link to a github repo or Code Sandbox?

Thanks in advance for your input!

Thread Thread
 
jeremywynn profile image
Jeremy Wynn

Sure, but no promises. I remember having to do things differently going from SPA to Universal mode, such as :key not working. But then I switched back to SPA since it was really what I was building.

Thread Thread
 
kp profile image
KP

@jeremywynn thank you.
To boil it down I am trying to do a page that has infinite scroll / loading, but in SSR mode (possibly with AsyncData). If you see how dev.to/ does it....on the homepage if you scroll down, new content is shown. If you then view source, all the new content shows up in the source. That's exactly what I need to do for SEO and have only got infinite loading working (I trigger the ajax from the store, but then the new content doesnt show up on viewing the source).

I have a codesandbox link here...it's kind of broken though. Been working on this for the past 2 weeks
codesandbox.io/s/codesandbox-nuxt-...
(this is my attempt at doing it in SSR mode with Intersection Observor, but it doesnt work. I do have infinite loading working in non-SSR mode, but I need it to work in SSR mode for SEO)

Let me know if you need any updates to the CodeSandBox link above or if anything isn't clear. Thanks for looking at this.