Next.js (SSG) & RSS feed

Publié il y a environ 1 an~3 min

With the proliferation of online content, access to information can sometimes be more time-consuming than one would like... Whether it is about general or more targeted news, there is however a simple way to facilitate daily digest: RSS feeds.

So why not set one up on your (static) website, and thus gain visibility?

Based on a Next.js project (SSG), we will see how to easily add an RSS feed for Markdown-based content.

Prerequisites

  1. Node.js (>= 10.13)
  2. A Next.js project (existing one or generated through npx create-next-app)

This website was the starting point for this article, so do not hesitate to refer to its source code for more details!

Generate the RSS feed

While it is quite possible to generate the stream file(s) completely manually, it is not necessary to reinvent the wheel! :man-cartwheeling: Here we will rely on the feed package, which will simplify the writing of our feed generation function:

1import fs from 'fs'
2import { Feed } from 'feed'
3import { remark } from 'remark'
4import html from 'remark-html'
5import gemoji from 'remark-gemoji'
6
7import { LOCALES } from '@i18n/constants'
8import { getPosts } from '@api/posts'
9import { getI18n } from '@api/i18n'
10
11const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL
12const AUTHOR_NAME = process.env.NEXT_PUBLIC_SITE_NAME
13const TWITTER_USERNAME = process.env.NEXT_PUBLIC_TWITTER_USERNAME
14
15const markdownToHtml = (markdown) =>
16 remark().use(html).use(gemoji).processSync(markdown).toString()
17
18const generateRssFeed = () => {
19 const author = {
20 name: AUTHOR_NAME,
21 link: `https://twitter.com/${TWITTER_USERNAME}`,
22 }
23
24 LOCALES.forEach((lang) => {
25 const { description } = getI18n(lang, 'home').home
26
27 const feed = new Feed({
28 title: `${AUTHOR_NAME}'s blog feed`,
29 description: Object.values(description).join(' '),
30 id: `${SITE_URL}/${lang}`,
31 link: `${SITE_URL}/${lang}`,
32 language: lang,
33 generator: 'Next.js using Feed',
34 feedLinks: {
35 rss2: `${SITE_URL}/${lang}/feed.xml`,
36 },
37 author,
38 })
39
40 const posts = getPosts(['title', 'excerpt', 'content'], lang)
41
42 posts.forEach((post) => {
43 feed.addItem({
44 title: post.title,
45 id: `${SITE_URL}/${lang}/blog/${post.slug}`,
46 link: `${SITE_URL}/${lang}/blog/${post.slug}`,
47 description: post.excerpt,
48 content: markdownToHtml(post.content),
49 date: new Date(post.date),
50 author: [author],
51 })
52 })
53
54 fs.mkdirSync(`./public/${lang}`, { recursive: true })
55 fs.writeFileSync(`./public/${lang}/feed.xml`, feed.rss2(), 'utf8')
56 })
57}
58
59generateRssFeed()

The code is split into two quite simple parts: first we generate the flow with its general information, then we loop through our content (posts) to generate our items.

The whole is then written into an .xml file, in RSS 2.0 format. Several formats are possible, however its excellent support makes the addition of other formats often superfluous.

Our content here is written using Markdown syntax. By relying on the remark ecosystem, we can easily add to our feed items the (exhaustive) content of our articles, converted to HTML. This is often a recommended practice, although not necessary.

Finally, in the case of a multilingual site, we'll just have to adapt the code, to generate as many files as supported languages.

Helping robots

To make life easier for browsers and other indexing robots, and to help them find our RSS feed, we should remember to add a link to it in the <head /> of our document:

1<link
2 rel="alternate"
3 type="application/rss+xml"
4 title={`${AUTHOR_NAME}'s blog feed`}
5 href="/feed.xml"
6/>

Updating the build script

Now that everything is in place, we still have to call this function. In the context of a SSG site, this call will be made, logically, during the build phase.

Our RSS script uses ES modules (ESM), to allow the import of functions used elsewhere in the project (such as content fetching). To use it in CLI, we need first to adapt our Webpack configuration via the next.config.js file:

1module.exports = {
2 // ...
3
4 webpack: (config, { dev, isServer }) => {
5 if (!dev && isServer) {
6 const originalEntry = config.entry;
7
8 config.entry = async () => {
9 const entries = await originalEntry()
10
11 // These scripts can import components from the app
12 // and use ES modules
13 return { ...entries, 'scripts/rss-generate': './src/scripts/rss.js' }
14 }
15 }
16
17 return config
18 }
19}

Our script will thus be compiled by Webpack in the .next/server folder. All that remains is to call it via another script from our package.json file, which will take care of generating our RSS feed:

1"scripts": {
2 // ...
3 "build": "next build && npm run rss:generate",
4 "rss:generate": "node ./.next/server/scripts/rss-generate"
5}

🎉

Et voilà ! Adding an RSS feed is a real plus for a website (especially a blog or other news site), allowing users to follow our content in a simple way, without having to juggle multiple tabs... It's also a good way to improve our visibility online, so why deprive yourself? 🙂