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

Published on

本文同步發佈於 it 邦幫忙 2022 iThome 鐵人賽

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 連結!