這是「Modern Blog 30 天」系列第 19 篇文章,上一篇我們做完 Sitemap 生成了,這篇接著來做非常相似的 RSS Feed 生成,讓部落格能被 RSS 訂閱。
這篇修改的程式碼如下:
我的個人網站裡也有此系列的好讀版,程式碼更易讀、也支援深色模式和側邊目錄,歡迎前往閱讀!
安裝 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.xml
、feed.json
和 feed.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>
這篇修改的程式碼如下:
恭喜你完成了 RSS Feed 生成!
目前部落格該有的功能都具備了,已經是個可以實際上線的專案了。
但後面還有 11 天,我們當然不會止步於此。後面 11 天我們會繼續加入更多炫砲功能,繼續朝向 Modern Blog 前進。
下一篇我們會修改文章內文 heading 小標題樣式,為每個 heading 加入 anchor 連結!