开发

Next.js站点生成RSS的坑

如何为Next.js项目生成RSS,以及如何避免一个低级错误

2023年4月14日
Next.js站点生成RSS的坑
本文共有715字,预计阅读时间3分钟
今天有人在我的博客上向我反应,这个站点的RSS文件把他服务器搞崩了,一看大小有95M。
之前我看到链接能打开,就没实际测试过。果然有大坑。
简单排查了一下,找到了原因,已经修复了这个问题。顺便分享一下如何给Next.js.js项目生成RSS,以及避免一个潜在的坑。

基本思路

Next.js官方并没有提供生成RSS的方法,所以我将使用feed来帮助我生成rss.xml文件。
大致流程是这样的:
  1. /page目录下新建一个rss.xml.tsx的文件,将通过/rss.xml的链接访问到RSS;
  2. 创建一个Feed对象,这将是RSS的主体;
  3. 使用getServerSideProps获取数据,添加到Feed里;
  4. 将数据写入。

代码

因为我使用了GraphQL,所以获取数据上可能会有点不同。
因为我希望每次访问RSS的时候,数据都能是最新的,所以会使用SSR来实现。
首先导入必要的依赖。
1import client from "@/apollo-client";
2import { gql } from "@apollo/client";
3import { Feed } from "feed";
4import { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from "next";
先创建一个Feed对象:
1const feed = new Feed({
2  title: "可可托海没有海的RSS",
3  description: "李大毛没有猫的个人网站",
4  id: "https://darmau.design/",
5  link: "https://darmau.design/",
6  language: "zh-CN",
7  image: "/img/default-cover.jpg",
8  favicon: "/img/logo.svg",
9  feedLinks: {
10    RSS2: "https://darmau.design/rss.xml",
11  },
12  copyright: ${new Date().getFullYear()} 李大毛`,
13  author: {
14    name: "李大毛",
15    link: "https://darmau.design/",
16  },
17});
再声明一个GraphQL查询语句:
1const GET_RSS = gql`
2  query Articles($sort: [String], $pagination: PaginationArg) {
3    articles(sort: $sort, pagination: $pagination) {
4      data {
5        attributes {
6          title
7          url
8          publishDate
9          description
10          main
11          cover {
12            data {
13              attributes {
14                url
15              }
16            }
17          }
18        }
19        id
20      }
21    }
22  }
23`;
然后增加一个getServerSideProps
1export const getServerSideProps: GetServerSideProps = async (context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<any>> => {
2      //code goes here
3}
接下来的代码都是在该函数内。
1const { res } = context;
2const { data } = await client.query({
3  query: GET_RSS,
4  variables: {
5    sort: ["publishDate:desc"],
6    pagination: {
7      limit: 10,
8    },
9  },
10});
11const articles = data.articles.data;
这段代码的作用是将一个文章的数据存进articles这个数组中,接下来分别将其添加进feed中:
1articles.forEach((article: ContentsProps) => {
2    feed.addItem({
3      title: article.attributes.title,
4      id: article.id,
5      link: `https://darmau.design/article/${article.attributes.url}`,
6      description: article.attributes.description,
7      content: article.attributes.main,
8      author: [
9        {
10          name: "李大毛",
11          link: "https://darmau.design/",
12        },
13      ],
14      contributor: [
15        {
16          name: "李大毛",
17          link: "https://darmau.design/",
18        },
19      ],
20      date: new Date(article.attributes.publishDate),
21      image: article.attributes.cover.data.attributes.url,
22    });
23  });
最后是设置缓存,将数据写入:
1const cacheMaxAgeUntilStaleSeconds = 5;
2const cacheMaxAgeStaleDataReturnSeconds = 30;
3res.setHeader(
4  "Cache-Control",
5  `public, s-maxage=${cacheMaxAgeUntilStaleSeconds}, stale-while-revalidate=${cacheMaxAgeStaleDataReturnSeconds}`
6  );
7
8res.setHeader("Content-Type", "text/xml");
9res.write(feed.rss2());
10res.end();
11
12return { props: {} };
最后在最外部,将整个函数默认到导出:
1export default function RSS() {}
这就完成了。
但!
有巨坑!

失控的尺寸

这个代码的问题就在于,每次访问该页面的时候,都会经历一次生成-添加RSS items的过程,导致feed充斥着大量重复条目,尺寸越来越大,这也是最终变成95M的原因。事实上这是个无限循环,有多大取决于你的电脑何时卡。
解决办法也很简单,就是在获取数据前,先将feed清空:
1feed.items = []
这下就没问题了。
完整代码可以访问这里

评论