iT邦幫忙

2022 iThome 鐵人賽

DAY 19
0
Modern Web

從零開始打造炫砲個人部落格,使用 Next.js、ContentLayer、i18next 等現代技術系列 第 19

使用 feed 生成 RSS Feed - Modern Next.js Blog 系列 #19

TL;DR

這是「Modern Blog 30 天」系列第 19 篇文章,上一篇我們做完 Sitemap 生成了,這篇接著來做非常相似的 RSS Feed 生成,讓部落格能被 RSS 訂閱。

這篇修改的程式碼如下:

https://github.com/Kamigami55/nextjs-tailwind-contentlayer-blog-starter/compare/day18-sitemap...day19-rss-feed

我的個人網站裡也有此系列的好讀版,程式碼更易讀、也支援深色模式和側邊目錄,歡迎前往閱讀!


加入 RSS Feed

安裝 feed 套件

pnpm add feed

修改 src/configs/siteConfigs.ts,新增 credit 和 email:

// ...
export const siteConfigs = {
  // ...
  credit: 'Stark Industries',
  email: 'stark@example.com',
};

新增 src/lib/generateRSS.ts

import { Feed } from 'feed';
import { writeFileSync } from 'fs';

import { siteConfigs } from '@/configs/siteConfigs';
import { allPostsNewToOld } from '@/lib/contentLayerAdapter';
import { getPostOGImage } from '@/lib/getPostOGImage';

export default function generateRSS() {
  const author = {
    name: siteConfigs.author,
    email: siteConfigs.email,
    link: siteConfigs.fqdn,
  };

  const feed = new Feed({
    title: siteConfigs.title,
    description: siteConfigs.description,
    id: siteConfigs.fqdn,
    link: siteConfigs.fqdn,
    image: siteConfigs.logoUrl,
    favicon: siteConfigs.logoUrl,
    copyright: `Copyright © 2015 - ${new Date().getFullYear()} ${
      siteConfigs.credit
    }`,
    feedLinks: {
      rss2: `${siteConfigs.fqdn}/feed.xml`,
      json: `${siteConfigs.fqdn}/feed.json`,
      atom: `${siteConfigs.fqdn}/atom.xml`,
    },
    author: author,
  });

  allPostsNewToOld.forEach((post) => {
    feed.addItem({
      id: siteConfigs.fqdn + post.path,
      title: post.title,
      link: siteConfigs.fqdn + post.path,
      description: post.description,
      image: getPostOGImage(post.socialImage),
      author: [author],
      contributor: [author],
      date: new Date(post.date),
      // content: post.body.html,
    });
  });

  writeFileSync('./public/feed.xml', feed.rss2());
  writeFileSync('./public/atom.xml', feed.atom1());
  writeFileSync('./public/feed.json', feed.json1());
}

修改 src/pages/index.tsx,將上面的 generateRSS() function 放進 getStaticProps 裡,在 pnpm build 時就會執行它來生成 RSS 檔案:

// ...
// 新增這行 Import
import generateRSS from '@/lib/generateRSS';

// ...

export const getStaticProps: GetStaticProps<Props> = () => {
  // ...
  
  // 新增下面這行
  generateRSS();

  return { props: { posts } };
};
// ...

修改 src/pages/_app.tsx,在全站加入 meta data 標註 RSS Feed 的路徑:

// ...
function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ThemeProvider attribute="class">
      <DefaultSeo
        // ...
        additionalLinkTags={[
          {
            rel: 'icon',
            href: siteConfigs.logoPath,
          },
          // 加入下面這兩個 link tag
          {
            rel: 'alternate',
            type: 'application/rss+xml',
            href: '/feed.xml',
          },
          {
            rel: 'alternate',
            type: 'application/atom+xml',
            href: '/atom.xml',
          },
        ]}
      />

      <LayoutWrapper>
        <Component {...pageProps} />
      </LayoutWrapper>
    </ThemeProvider>
  );
}

export default MyApp;

修改 .gitignore.eslintignore.prettierignore,忽略生成的 RSS Feed:

# ...
# 加入下面這 3 條規則
# RSS related files (generated by generateRSS.js)
/public/atom.xml
/public/feed.xml
/public/feed.json

成果

完成了!使用 pnpm build,執行完就會看到 /public 路徑裡多了 atom.xmlfeed.jsonfeed.xml 了,三種不同的 RSS 格式。

生成的內容如下:

public/atom.xml

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app</id>
    <title>Next.js Tailwind Contentlayer Blog Starter</title>
    <updated>2022-10-04T15:38:07.971Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <author>
        <name>Tony Stark</name>
        <email>stark@example.com</email>
        <uri>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app</uri>
    </author>
    <link rel="alternate" href="https://nextjs-tailwind-contentlayer-blog-starter.vercel.app"/>
    <link rel="self" href="https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/atom.xml"/>
    <subtitle>Blog starter template with modern frontend technologies like Next.js, Tailwind CSS, Contentlayer, i18Next</subtitle>
    <logo>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/logo.png</logo>
    <icon>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/logo.png</icon>
    <rights>Copyright © 2015 - 2022 Stark Industries</rights>
    <entry>
        <title type="html"><![CDATA[Post with images]]></title>
        <id>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/post-with-images</id>
        <link href="https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/post-with-images"/>
        <updated>2022-09-02T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[My post with images]]></summary>
        <author>
            <name>Tony Stark</name>
            <email>stark@example.com</email>
            <uri>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app</uri>
        </author>
        <contributor>
            <name>Tony Stark</name>
            <email>stark@example.com</email>
            <uri>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app</uri>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Post with code]]></title>
        <id>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/post-with-code</id>
        <link href="https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/post-with-code"/>
        <updated>2022-09-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[My post with code]]></summary>
        <author>
            <name>Tony Stark</name>
            <email>stark@example.com</email>
            <uri>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app</uri>
        </author>
        <contributor>
            <name>Tony Stark</name>
            <email>stark@example.com</email>
            <uri>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app</uri>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Markdown demo]]></title>
        <id>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/markdown-demo</id>
        <link href="https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/markdown-demo"/>
        <updated>2022-08-31T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[This is a demo of Markdown]]></summary>
        <author>
            <name>Tony Stark</name>
            <email>stark@example.com</email>
            <uri>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app</uri>
        </author>
        <contributor>
            <name>Tony Stark</name>
            <email>stark@example.com</email>
            <uri>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app</uri>
        </contributor>
    </entry>
    <entry>
        <title type="html"><![CDATA[Sample post]]></title>
        <id>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/sample-post</id>
        <link href="https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/sample-post"/>
        <updated>2022-08-30T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[My first post]]></summary>
        <author>
            <name>Tony Stark</name>
            <email>stark@example.com</email>
            <uri>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app</uri>
        </author>
        <contributor>
            <name>Tony Stark</name>
            <email>stark@example.com</email>
            <uri>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app</uri>
        </contributor>
    </entry>
</feed>

public/feed.json

{
    "version": "https://jsonfeed.org/version/1",
    "title": "Next.js Tailwind Contentlayer Blog Starter",
    "home_page_url": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app",
    "feed_url": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/feed.json",
    "description": "Blog starter template with modern frontend technologies like Next.js, Tailwind CSS, Contentlayer, i18Next",
    "icon": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/logo.png",
    "author": {
        "name": "Tony Stark",
        "url": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app"
    },
    "items": [
        {
            "id": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/post-with-images",
            "url": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/post-with-images",
            "title": "Post with images",
            "summary": "My post with images",
            "image": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/images/anita-chong-unsplash.jpg",
            "date_modified": "2022-09-02T00:00:00.000Z",
            "author": {
                "name": "Tony Stark",
                "url": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app"
            }
        },
        {
            "id": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/post-with-code",
            "url": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/post-with-code",
            "title": "Post with code",
            "summary": "My post with code",
            "image": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/og-image.png",
            "date_modified": "2022-09-01T00:00:00.000Z",
            "author": {
                "name": "Tony Stark",
                "url": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app"
            }
        },
        {
            "id": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/markdown-demo",
            "url": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/markdown-demo",
            "title": "Markdown demo",
            "summary": "This is a demo of Markdown",
            "image": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/og-image.png",
            "date_modified": "2022-08-31T00:00:00.000Z",
            "author": {
                "name": "Tony Stark",
                "url": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app"
            }
        },
        {
            "id": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/sample-post",
            "url": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/sample-post",
            "title": "Sample post",
            "summary": "My first post",
            "image": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/og-image.png",
            "date_modified": "2022-08-30T00:00:00.000Z",
            "author": {
                "name": "Tony Stark",
                "url": "https://nextjs-tailwind-contentlayer-blog-starter.vercel.app"
            }
        }
    ]
}

public/feed.xml

<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
    <channel>
        <title>Next.js Tailwind Contentlayer Blog Starter</title>
        <link>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app</link>
        <description>Blog starter template with modern frontend technologies like Next.js, Tailwind CSS, Contentlayer, i18Next</description>
        <lastBuildDate>Tue, 04 Oct 2022 15:38:07 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Next.js Tailwind Contentlayer Blog Starter</title>
            <url>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/logo.png</url>
            <link>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app</link>
        </image>
        <copyright>Copyright © 2015 - 2022 Stark Industries</copyright>
        <item>
            <title><![CDATA[Post with images]]></title>
            <link>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/post-with-images</link>
            <guid>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/post-with-images</guid>
            <pubDate>Fri, 02 Sep 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[My post with images]]></description>
            <author>stark@example.com (Tony Stark)</author>
            <enclosure url="https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/images/anita-chong-unsplash.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Post with code]]></title>
            <link>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/post-with-code</link>
            <guid>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/post-with-code</guid>
            <pubDate>Thu, 01 Sep 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[My post with code]]></description>
            <author>stark@example.com (Tony Stark)</author>
            <enclosure url="https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/og-image.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Markdown demo]]></title>
            <link>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/markdown-demo</link>
            <guid>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/markdown-demo</guid>
            <pubDate>Wed, 31 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[This is a demo of Markdown]]></description>
            <author>stark@example.com (Tony Stark)</author>
            <enclosure url="https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/og-image.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Sample post]]></title>
            <link>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/sample-post</link>
            <guid>https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/posts/sample-post</guid>
            <pubDate>Tue, 30 Aug 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[My first post]]></description>
            <author>stark@example.com (Tony Stark)</author>
            <enclosure url="https://nextjs-tailwind-contentlayer-blog-starter.vercel.app/og-image.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>

這篇修改的程式碼如下:

https://github.com/Kamigami55/nextjs-tailwind-contentlayer-blog-starter/compare/day18-sitemap...day19-rss-feed

References

下一篇

恭喜你完成了 RSS Feed 生成!

目前部落格該有的功能都具備了,已經是個可以實際上線的專案了。

但後面還有 11 天,我們當然不會止步於此。後面 11 天我們會繼續加入更多炫砲功能,繼續朝向 Modern Blog 前進。

下一篇我們會修改文章內文 heading 小標題樣式,為每個 heading 加入 anchor 連結!


上一篇
使用 next-sitemap 生成 Sitemap - Modern Next.js Blog 系列 #18
下一篇
為內文小標題加入 anchor 錨點連結 - Modern Next.js Blog 系列 #20
系列文
從零開始打造炫砲個人部落格,使用 Next.js、ContentLayer、i18next 等現代技術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言