Next.js Route Handlers ライブラリなしでRSSフィードを作る
はじめに
今回は、自分のブログにRSSを導入しようと思います。今はSNSがあるので、わざわざRSSリーダーを使う機会は減ったかもしれませんが、一部の人にとっては意味あるものだと思うので実装してみました。
RSSとは
まずRSS(Rich Site Summary、Really Simple Syndication)とは、Webサイトの新着記事や、更新情報の要約、見出しなどを配信する技術で、XML(eXtensible Markup Language)ベースの文書フォーマットの一種です。RSSリーダーと呼ばれるツールや、RSSに対応したブラウザを使用することで、Webサイトにアクセスしなくても、新着記事や更新情報を自動的に取得して、興味のある記事を簡単に閲覧できます。
実装
ネットやChatGPTに聞いてもfeedやnode-rssを使ってやりましょうという記事が出てきましたが、ライブラリは全くメンテナンスされていないですし、RSSの仕組み自体はそんなに難しいものだと思ったので、ライブラリは特に使わず実装しました。
RSS生成の手順
Next.jsのRoute Handlersを使って実装します。
- 記事データの取得: CMS(私のブログはNotionで管理しているためNotion API)から記事の一覧を取得。Notion APIの扱い方はこの記事では取り扱いません。
- RSSのXML生成: RSSの文字列を生成。
-
API作成:
app/rss.xml.route.tsでRSSフィードを作成。 - ヘッダーにリンクを追加: RSSのリンクを追加。
コード
import { Client } from "@notionhq/client";
import { NextResponse } from "next/server";
const notion = new Client({
auth: process.env.NOTION_TOKEN,
});
const getArticles = async () => {
const filterObject = {
property: "status",
status: {
equals: "published",
},
};
const response = await notion.databases.query({
database_id: process.env.NOTION_DATABASE_ID as string,
sorts: [
{
// 公開日の降順に並び替える
property: "published_at",
direction: "descending",
},
],
filter: filterObject,
});
const posts = response.results;
const postsProperties = posts.map((post: any) => {
const title = post.properties.title.title[0]?.plain_text;
const summary = post.properties.summary.rich_text[0]?.plain_text;
const slug = post.properties.slug.rich_text[0]?.plain_text;
const publishedAt = post.properties.published_at.date.start;
return { title, slug, publishedAt, summary };
});
return postsProperties;
};
export async function GET() {
// 1. Notion APIから記事一覧を取得
const articles = await getArticles();
// 2. RSSのXML生成
const rss = `<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>br-to DevLog</title>
<link>${process.env.NEXT_PUBLIC_BASE_URL}</link>
<description>This blog is br-to's devlog.</description>
<atom:link href="${process.env.NEXT_PUBLIC_BASE_URL}/rss.xml" rel="self" type="application/rss+xml" />
<language>ja</language>
${articles
.map(
({ title, slug, publishedAt, summary }) => `
<item>
<title>${title}</title>
<link>${process.env.NEXT_PUBLIC_BASE_URL}/blog/${slug}</link>
<guid isPermaLink="true">${process.env.NEXT_PUBLIC_BASE_URL}/blog/${slug}</guid>
<description>${summary}</description>
<pubDate>${new Date(publishedAt).toUTCString()}</pubDate>
</item>`,
)
.join("")}
</channel>
</rss>`;
// 3. API作成
return new NextResponse(rss, {
headers: {
"Content-Type": "text/xml; charset=utf-8",
"Cache-Control": "public, s-maxage=3600, stale-while-revalidate=86400",
},
});
}
import Image from "next/image";
import Link from "next/link";
import styles from "./Header.module.css";
export function Header() {
return (
<header className={styles.header}>
<div className={styles.container}>
<nav className={styles.nav}>
...
// リンクを追加
<Link href="/rss.xml" className={styles.item}>
<Image src="/rss.svg" width={20} height={20} alt="RSS" />
</Link>
</div>
</nav>
</div>
</header>
);
}
困ったところ
導入の際、苦戦したところはほとんどないですが、RSSのContent-Typeに関しては少しどうするべきかわからず困りました。
Content-Typeをapplication/rss+xmlにするのがRSSフィードであることを正確に示すものとして推奨されているため、最初こちらで実装しましたが、スマホだとブラウザによって挙動が異なりました。
- safari: App Storeに飛ばそうとするダイアログを表示
- chrome: rss.xmlをダウンロードしようとするダイアログが表示
この件は色々探しても詳しい記事が全く出なかったのでContent-Typeをapplication/rss+xmlにすることはやめて、Zennに合わせてtext/xmlに変更しました。こうすればどのブラウザでも問題なく/rss.xmlを表示することができました。
RSSフォーマットの確認
作ったRSSのフォーマットが問題ないかは、https://validator.w3.org/feed/ にレスポンスをそのまま投げて確認しました。
動作検証
導入したものが実際に動いているかどうかは、Feedlyという代表的なRSSリーダーにhttps://www.br-to.dev/rss.xmlを登録して確認しました。
Feedlyは会員登録しないとフィードを確認できなかったので注意してください。
まとめ
以上でライブラリを使わずNext.jsのRoute Handlersを使ってRSSを導入することができました。
ここまで読んでいただき、ありがとうございました。
Discussion