Next.js Route Handlers ライブラリなしでRSSフィードを作る
2025/04/06 公開
はじめに
今回は、自分のブログに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のリンクを追加。
コード
app/rss.xml/route.ts
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", }, }); }
src/components/Header.tsx
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を導入することができました。引き続きブログ改善した
ここまで読んでいただき、ありがとうございました。