ContentlayerでMarkdownブログのRSSフィードを作る

## 序

- [Contentlayer](https://contentlayer.dev/)と[Next.js](https://nextjs.org/)で[Markdown個人ブログ](http://kedama-t.netlify.app)を作ったのですが、RSSフィードの実装がまだだったので、組み込んでみました。

#### サイト
http://kedama-t.netlify.app

#### リポジトリ
https://github.com/kedama-t/blog

## 方針

- 大変ありがたいことに、Contentlayerには[ビルド成功時にコールバック関数を呼べる仕組み](https://contentlayer.dev/docs/reference/source-files/make-source-a5ba4922#onsuccess)があります。
  - これを利用してビルド時に`public/rss.xml`が生成されるようにします。

## 実装

- `rss.ts`ファイルにRSSフィード生成のためのコードを実装します。
  - 外から`import`している`sortFunction`や`DESCRIPTION`等の定数については、ここでは割愛します。

### `rss.ts`

```ts
import fs from 'fs';
import type { Post } from './.contentlayer/generated';
import { sortFunction } from './src/lib/utils';
import { DESCRIPTION, TITLE, URL_BASE } from './src/lib/const';

const generateRss = (allPosts: Post[]) => {
  const path = './public/rss.xml'
  const items = allPosts.sort(sortFunction).splice(0, 10).map(post => {
    return `<item>
<guid>${URL_BASE}${post.url}</guid>
<title>${post.title}</title>
<description>${post.description}</description>
<link>${URL_BASE}${post.url}</link>
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
</item>`
  })

  const rss = `<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>${TITLE}</title>
<link>${URL_BASE}</link>
<description>${DESCRIPTION}</description>
<atom:link href="${URL_BASE}/rss.xml" rel="self" type="application/rss+xml" />
${items.join('')}
</channel>
</rss>`
  fs.writeFileSync(path, rss)
}

export default generateRss;

```

- 見ての通りで、Contentlayerで生成したドキュメントから最新の記事10件を取り出し、テンプレートリテラルでひたすら埋め込みます。


### `contentlayer.config.ts`

- Contentlayerの`makeSource`の引数に渡すオブジェクトに、`onSuccess`を生やして関数を入れておくと、ドキュメントのビルド成功時に実行してくれます。

```ts
import { makeSource } from 'contentlayer/source-files';
import generateRss from "./rss";

/* ... 中略 ... */

export default makeSource({
  contentDirPath: 'posts',
  documentTypes: [Post],
  markdown: {
    //@ts-expect-error
    remarkPlugins: [remarkDirective, remarkDirectiveRehype, remarkGfm],
    rehypePlugins: [
      rehypeSlug,
      [rehypeAutolinkHeadings, { behavior: 'append' }],
      [rehypePrettyCode, { ...prettyCodeOptions }],
    ],
  },
  onSuccess: (async (importData) => {
    // generate rss feed
    const { allPosts } = await importData()
    generateRss(allPosts);
  })
});
```

## 出来るもの

- `./public/rss.xml`にこのような感じでファイルが生えます。
  - [W3CのFeed Validator](https://validator.w3.org/feed/)に投げたら`This is a valid RSS feed.`と出たので、大丈夫なはず。

```xml
<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
<channel>
<title>Crumbs on the table</title>
<link>https://kedama-t.netlify.app</link>
<description>技術・思考・読書のメモブログ</description>
<atom:link href="https://kedama-t.netlify.app/rss.xml" rel="self" type="application/rss+xml"/>
<item>
<guid>https://kedama-t.netlify.app/posts/tech/01.create_own_blog</guid>
<title>ブログの構築</title>
<description>Next.js + Tailwind CSS + Contentlayer で自分だけのMarkdownブログを作る</description>
<link>https://kedama-t.netlify.app/posts/tech/01.create_own_blog</link>
<pubDate>Fri, 01 Dec 2023 00:00:00 GMT</pubDate>
</item>
<item>
<guid>https://kedama-t.netlify.app/posts/idea/01.remark_memo</guid>
<title>remarkについてのメモ</title>
<description>undefined</description>
<link>https://kedama-t.netlify.app/posts/idea/01.remark_memo</link>
<pubDate>Thu, 30 Nov 2023 00:00:00 GMT</pubDate>
</item>
<item>
<guid>https://kedama-t.netlify.app/posts/booklog/01.booklog</guid>
<title>積本状況 in 202311</title>
<description>あるいはブラックフライデーの戦利品</description>
<link>https://kedama-t.netlify.app/posts/booklog/01.booklog</link>
<pubDate>Tue, 28 Nov 2023 00:00:00 GMT</pubDate>
</item>
<item>
<guid>https://kedama-t.netlify.app/posts/tech/02.app_idea_in_202311</guid>
<title>作りたいもの in 202311</title>
<description>いつか作るかもしれないもの</description>
<link>https://kedama-t.netlify.app/posts/tech/02.app_idea_in_202311</link>
<pubDate>Tue, 28 Nov 2023 00:00:00 GMT</pubDate>
</item>
</channel>
</rss>
```

## 終わりに

- Contentlayer、かゆいところに手が届いていいですね。
- RSSってあんまり今時じゃないかもしれないですが、使うと案外便利ですよね。