Next.js站点生成RSS的坑

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

开发

-
学习如何用Next.js生成RSS文件并避免出现尺寸巨大的问题。通过使用feed库,创建Feed对象并获取最新数据,最后在getServerSideProps函数中写入并设置缓存。确保在添加RSS条目前清空feed以避免无限循环。详细内容和代码示例可在页面中找到。
Next.js站点生成RSS的坑

今天有人在我的博客上向我反应,这个站点的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来实现。

首先导入必要的依赖。

             import client from "@/apollo-client";
import { gql } from "@apollo/client";
import { Feed } from "feed";
import { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from "next";
           

先创建一个 Feed 对象:

             const feed = new Feed({
  title: "可可托海没有海的RSS",
  description: "李大毛没有猫的个人网站",
  id: "https://darmau.design/",
  link: "https://darmau.design/",
  language: "zh-CN",
  image: "/img/default-cover.jpg",
  favicon: "/img/logo.svg",
  feedLinks: {
    RSS2: "https://darmau.design/rss.xml",
  },
  copyright: `©${new Date().getFullYear()} 李大毛`,
  author: {
    name: "李大毛",
    link: "https://darmau.design/",
  },
});
           

再声明一个GraphQL查询语句:

             const GET_RSS = gql`
  query Articles($sort: [String], $pagination: PaginationArg) {
    articles(sort: $sort, pagination: $pagination) {
      data {
        attributes {
          title
          url
          publishDate
          description
          main
          cover {
            data {
              attributes {
                url
              }
            }
          }
        }
        id
      }
    }
  }
`;
           

然后增加一个 getServerSideProps

             export const getServerSideProps: GetServerSideProps = async (context: GetServerSidePropsContext): Promise<GetServerSidePropsResult<any>> => {
      //code goes here
}
           

接下来的代码都是在该函数内。

             const { res } = context;
const { data } = await client.query({
  query: GET_RSS,
  variables: {
    sort: ["publishDate:desc"],
    pagination: {
      limit: 10,
    },
  },
});
const articles = data.articles.data;
           

这段代码的作用是将一个文章的数据存进 articles 这个数组中,接下来分别将其添加进 feed 中:

             articles.forEach((article: ContentsProps) => {
    feed.addItem({
      title: article.attributes.title,
      id: article.id,
      link: `https://darmau.design/article/${article.attributes.url}`,
      description: article.attributes.description,
      content: article.attributes.main,
      author: [
        {
          name: "李大毛",
          link: "https://darmau.design/",
        },
      ],
      contributor: [
        {
          name: "李大毛",
          link: "https://darmau.design/",
        },
      ],
      date: new Date(article.attributes.publishDate),
      image: article.attributes.cover.data.attributes.url,
    });
  });
           

最后是设置缓存,将数据写入:

             const cacheMaxAgeUntilStaleSeconds = 5;
const cacheMaxAgeStaleDataReturnSeconds = 30;
res.setHeader(
  "Cache-Control",
  `public, s-maxage=${cacheMaxAgeUntilStaleSeconds}, stale-while-revalidate=${cacheMaxAgeStaleDataReturnSeconds}`
  );

res.setHeader("Content-Type", "text/xml");
res.write(feed.rss2());
res.end();

return { props: {} };
           

最后在最外部,将整个函数默认到导出:

             export default function RSS() {}
           

这就完成了。

但!

有巨坑!

失控的尺寸

这个代码的问题就在于,每次访问该页面的时候,都会经历一次生成-添加RSS items的过程,导致 feed 充斥着大量重复条目,尺寸越来越大,这也是最终变成95M的原因。事实上这是个无限循环,有多大取决于你的电脑何时卡。

解决办法也很简单,就是在获取数据前,先将 feed 清空:

             feed.items = []
           

这下就没问题了。

完整代码可以访问 这里