RSSフィード自動生成 — feed パッケージでブログをRSS対応にする
Next.js の静的ブログに RSS フィードを追加するのは意外に簡単で、feed パッケージと postbuild スクリプトを組み合わせれば ビルドのたびに自動で feed.xml が生成される。本記事ではこのブログ(Maemaemae)での実装をそのまま解説する。
なぜ RSS フィードが必要か
- RSS リーダーで購読できる: Feedly、NetNewsWire、Reeder などのユーザーが新記事を見逃さない
- SEO 的な恩恵: Google が RSS を認識してクロール頻度を上げることがある
- 他サービスへの連携: Zapier や IFTTT で RSS をトリガーにした自動投稿が可能
- ヘッドレス CMS 的な活用: RSS を API 代わりに使うサービスがある
静的サイトはサーバーサイドのエンドポイントを作れないため、ビルド時に静的ファイルとして生成しておくのが唯一の選択肢だ。
技術スタック
| 用途 | パッケージ |
|---|---|
| RSS/Atom/JSON Feed 生成 | feed |
| Markdown フロントマター解析 | gray-matter |
| スクリプト実行タイミング制御 | npm の postbuild フック |
セットアップ
パッケージのインストール
npm install feed
# gray-matter はすでにインストール済みの想定
ディレクトリ構成
blog/
├── posts/ # Markdown 記事
├── tool/
│ └── generate-feed.mjs # フィード生成スクリプト
├── out/ # next build の出力先
│ └── feed.xml # 生成されるフィード
└── package.json
スクリプトの実装
tool/generate-feed.mjs の全文を解説する。
import fs from "fs";
import path from "path";
import matter from "gray-matter";
import { Feed } from "feed";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.join(__dirname, "..");
const SITE_URL = "https://maemaemae.blog";
ESM(.mjs)で書いているため __dirname が使えない。fileURLToPath(import.meta.url) で代替する。
記事データの取得
function getPostData() {
const postsDirectory = path.join(ROOT, "posts");
const filenames = fs.readdirSync(postsDirectory);
return filenames
.map((filename) => {
const filePath = path.join(postsDirectory, filename);
const fileContents = fs.readFileSync(filePath, "utf-8");
const { data } = matter(fileContents);
return {
slug: filename.replace(/\.md$/, ""),
title: data.title,
description: data.description || "",
date: data.date,
image: data.image || null,
tags: data.tags || [],
};
})
.sort((a, b) => (new Date(a.date) > new Date(b.date) ? -1 : 1));
}
gray-matter でフロントマターだけを抽出している。本文は RSS フィードに含めないためパースしない(高速化のため)。日付の降順ソートは updated フィールド(フィードの最終更新日)に最新記事の日付を使うために必要。
フィードの生成
function generateFeed() {
const posts = getPostData();
const feed = new Feed({
title: "Maemaemae",
description: "Maemaemae のブログ",
id: SITE_URL + "/",
link: SITE_URL + "/",
language: "ja",
favicon: SITE_URL + "/favicon.ico",
copyright: `All rights reserved ${new Date().getFullYear()}, Maemaemae`,
updated: posts.length > 0 ? new Date(posts[0].date) : new Date(),
feedLinks: {
rss2: SITE_URL + "/feed.xml",
},
});
posts.forEach((post) => {
const url = `${SITE_URL}/posts/${post.slug}`;
feed.addItem({
title: post.title,
id: url,
link: url,
description: post.description,
date: new Date(post.date),
image: post.image ? SITE_URL + post.image : undefined,
category: post.tags.map((tag) => ({ name: tag })),
});
});
const outDir = path.join(ROOT, "out");
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir, { recursive: true });
}
fs.writeFileSync(path.join(outDir, "feed.xml"), feed.rss2());
console.log("feed.xml generated successfully.");
}
generateFeed();
ポイント:
idとlinkはどちらも URL を指定する(RSS 2.0 仕様上の<guid>に使われる)imageは絶対 URL でなければならない。/images/xxx.jpgのような相対パスはサイト URL を頭に付けて変換するcategoryは{ name: string }の配列形式
postbuild への組み込み
// package.json
{
"scripts": {
"build": "next build",
"postbuild": "next-sitemap && node tool/generate-feed.mjs"
}
}
npm は build スクリプトの実行後に自動的に postbuild を実行する。next build → next-sitemap → generate-feed.mjs の順で動く。
生成される feed.xml の構造
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" ...>
<channel>
<title>Maemaemae</title>
<link>https://maemaemae.blog/</link>
<description>Maemaemae のブログ</description>
<language>ja</language>
<lastBuildDate>Fri, 07 Mar 2026 00:00:00 GMT</lastBuildDate>
<item>
<title>RSSフィード自動生成 ...</title>
<link>https://maemaemae.blog/posts/blog_20260307</link>
<guid>https://maemaemae.blog/posts/blog_20260307</guid>
<description>Next.js 静的ブログに ...</description>
<pubDate>Fri, 07 Mar 2026 00:00:00 GMT</pubDate>
<category>Next.js</category>
<category>Node.js</category>
</item>
...
</channel>
</rss>
HTML への feed.xml リンク追加
RSS リーダーがフィードを自動検出できるように、<head> に <link> タグを追加する。
// src/app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ja">
<head>
<link
rel="alternate"
type="application/rss+xml"
title="Maemaemae RSS Feed"
href="/feed.xml"
/>
</head>
<body>{children}</body>
</html>
);
}
これにより、ブラウザや RSS リーダーがページを開いたときにフィードを自動検出できる。
ハマりやすいポイント
1. .mjs での __dirname が使えない
CommonJS の __dirname は ESM では未定義。以下で代替する。
import { fileURLToPath } from "url";
import path from "path";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
Node.js 20.11.0 以降では import.meta.dirname が直接使えるため、上記の変換は不要になっている。
// Node.js 20.11.0+
const ROOT = path.join(import.meta.dirname, "..");
2. 画像 URL は必ず絶対 URL に変換する
/images/ogp.jpg のような相対パスをそのまま image に渡すと、RSS リーダーが画像を取得できない。
// NG
image: post.image // "/images/ogp.jpg"
// OK
image: post.image ? SITE_URL + post.image : undefined
3. out/ ディレクトリが存在しない場合がある
next build が先に実行されていれば out/ は存在するが、スクリプトを単体で実行するときに存在しない場合がある。fs.mkdirSync(outDir, { recursive: true }) でガードしておく。
4. date の型変換を忘れない
フロントマターの date は "2026-03-07" という文字列で取得される。feed.addItem の date プロパティは Date 型を要求するため new Date(post.date) で変換する。
5. postbuild は npm run build でしか動かない
postbuild は npm の lifecycle スクリプトなので node tool/generate-feed.mjs を直接呼んでもフックは起動しない。開発中に単体テストしたい場合は直接 node tool/generate-feed.mjs を実行する(out/ に書き出されることに注意)。
動作確認
npm run build
# → next build
# → next-sitemap
# → node tool/generate-feed.mjs
# → "feed.xml generated successfully."
ls out/feed.xml # ファイルが生成されていることを確認
ローカルで内容を確認するには W3C の RSS バリデーターか、Feedly の「フィードを追加」に file:///path/to/out/feed.xml を試してみるとよい(ローカルファイルは多くのリーダーで動作しないためデプロイ後に確認する)。
関連ツールとの比較
Next.js ブログで RSS を生成する方法は複数あるため、選択肢を整理しておく。
| 手法 | パッケージ / API | 特徴 |
|---|---|---|
| 静的ファイル生成(今回) | feed + postbuild | output: "export" の静的サイトに唯一対応。ビルド時のみ実行 |
| Route Handler(App Router) | feed + route.ts | サーバー機能が使える場合に有効。/feed.xml にリクエストが来るたびに生成 |
rss パッケージ | rss | RSS 2.0 のみ対応。最終更新が古く(2019年)、feed パッケージの方が現在は推奨 |
feed-rs(Rust) | — | Node.js エコシステム外。Remix や Deno での利用向け |
静的エクスポート(output: "export")を使っている場合は Route Handler が使えないため、今回のように postbuild スクリプトで out/ に直接書き出す方法が正しい選択だ。
まとめ
| ステップ | やること |
|---|---|
| 1. インストール | npm install feed |
| 2. スクリプト作成 | tool/generate-feed.mjs で記事一覧→フィード変換 |
| 3. postbuild 登録 | package.json の postbuild に node tool/generate-feed.mjs を追加 |
| 4. head タグ追加 | layout.tsx に <link rel="alternate" ...> を追加 |
| 5. デプロイ確認 | /feed.xml にアクセスして XML が返ることを確認 |
feed パッケージは RSS 2.0 以外に Atom 1.0・JSON Feed にも対応しているため、feed.atom1() や feed.json1() に差し替えるだけで複数フォーマットの同時生成も可能だ。