RSSフィードが壊れて誰も気づかない事故をClaude Codeで防ぐ
RSSが配信されない、XMLが壊れて読者の購読が切れる。Claude CodeでRSS 2.0/Atomを絶対URL・エスケープ・検証込みで安全に作る手順を、失敗談つきで解説。
ある朝、読者から「最近の記事、RSSに出てこないんですけど」と連絡が来ました。
ヒヤッとして自分のフィードを開いたら、/rss.xml は200を返しているのに、中身は2週間前で止まっていた。タイトルに R&D という文字が入った記事を出した瞬間、& がエスケープされずにXMLが壊れ、リーダーが「不正なフィード」として読み込みを止めていたんです。
ブラウザで見ると一見ちゃんと表示される。だから気づかない。これがRSSのいちばん怖いところでした。
この記事の要点
- RSSフィードは「作る」より「壊れない状態を検証し続ける」ほうが実務では大事。ブラウザ表示だけでは壊れたXMLを見逃す
- 事故の9割は3つに集約される。XMLエスケープ漏れ・相対URL・日付フォーマットの曖昧さ。ここだけ潰せば大半は防げる
- Claude Codeに丸投げせず、「絶対URL・エスケープ・draft除外・件数制限」を仕様として固定してから書かせると安定する
- Astroなら公式の
@astrojs/rssで骨組みを作り、Node製の検証スクリプトとW3C Feed Validatorで二重チェックするのが現実的 - 多言語サイトは「日本語記事のリンクを英語フィードに混ぜる」事故が起きやすい。collection・URL prefix・language を必ず揃える
そもそもRSSは、検索やSNSに依存しない最後の直通路
ブログを公開しても、読者が毎回トップページを見に来てくれるわけじゃありません。記事の入口は、検索、SNS、ニュースレター、Slack通知と、バラバラに散らばっています。
その中でRSSフィードだけが、検索アルゴリズムやSNSのタイムライン表示順に左右されません。読者が一度購読すれば、こちらの更新がそのまま手元に届く。地味だけど、いちばん途切れにくい配信口です。
中身はシンプルで、記事のタイトル・URL・概要・公開日を、XMLという機械が読みやすい形式で並べただけのテキストファイルです。XMLはタグで意味を表す点でHTMLに似ていますが、閉じタグやエスケープのルールがずっと厳密で、1文字壊れただけで全体が読めなくなります。AtomはRSSに近いもう一つのフィード形式で、RFC 4287として仕様が決まっています。最初はRSS 2.0だけ作って、必要になったらAtomを足す。その順番で十分です。
この記事では、Claude Codeに「RSS作って」と丸投げするのではなく、絶対URL、XMLエスケープ、日付、キャッシュ、多言語、検証まで含めて、壊れないフィードを作る流れを書きます。記事データそのものの管理はClaude CodeでブログCMSを作る、検索からの流入はClaude Code SEO最適化、検索エンジン向けのXMLはClaude Codeでサイトマップを生成すると合わせて読むと、配信まわりの全体像がつながります。
一次情報は必ず自分で確認してください。RSS 2.0はRSS Advisory Boardの仕様、AtomはIETFのRFC 4287、検証はW3C Feed Validation Service、Astroの実装はAstro公式のRSSレシピが基準です。
RSSが効く場面を、具体的に4つ
抽象的に「便利です」と言われても動けないので、僕が実際に効果を感じた場面を挙げます。
1. 技術ブログのリピート読者を逃さない。 RSSリーダーを使う人は今や少数派に見えますが、開発者・研究者・編集者みたいに「情報収集が仕事」の層には根強く残っています。FeedlyやInoreaderに新記事が流れれば、SNSの投稿が埋もれても記事は届きます。
2. 社内ナレッジの更新通知に使う。 障害報告、リリースノート、セキュリティ注意喚起をRSSにしておくと、Slack Botや社内ポータルが同じフィードを読めます。人間向けのページと機械向けのフィードを同じ記事データから作れるので、二重入力が消えます。
3. 多言語サイトで言語別に届ける。 日本語・英語・中国語で同じslugの記事を出しているなら、/rss.xml だけでなく /en/rss.xml、/zh/rss.xml と言語別フィードを用意する。読者は自分の言語だけ購読できて、リーダーに別言語の記事が混ざりません。
4. 収益導線を保守する。 研修やテンプレート販売につなげるサイトでは、新記事がRSSに載らないだけでリピート読者との接点が1本減ります。広告だけでなく、Claude Code研修・導入相談や教材ページへの導線も、公開後に届いて初めて意味を持ちます。
どれも共通しているのは、「公開した瞬間」ではなく「読者の手元に届いた瞬間」に価値が出るという点です。だからフィードが静かに壊れていると、損失に気づけません。
実装の前に、仕様を1枚で決めておく
Claude Codeに依頼する前に、RSSの仕様を1枚にまとめます。ここを曖昧にしたまま「いい感じに作って」と頼むと、相対URL・壊れたXML・未来日付・古い記事順・多言語混在が、まとめて返ってきます。
| 項目 | 推奨 | 理由 |
|---|---|---|
| 形式 | まずRSS 2.0、必要ならAtomも追加 | 対応リーダーが多く実装が単純 |
| URL | すべて絶対URL | リーダーやクローラーが正しく解決できる |
| 日付 | RSSは toUTCString()、Atomは toISOString() | RSSはRFC 822系、AtomはRFC 3339系に寄せる |
| 本文 | 初期はdescription中心 | 全文HTMLはsanitizeと画像URL処理が必要 |
| 件数 | 20〜50件 | 多すぎると取得と検証が重くなる |
| キャッシュ | Cache-Control を付ける | CDNとリーダーの再取得が安定する |
| 検証 | W3C Feed Validatorと自動テスト | ブラウザ表示だけでは壊れたXMLを見逃す |
フィードの最低限の骨格は rss → channel → item の3階層です。channel がサイト全体の情報、item が記事1件の情報。guid は記事を一意に識別する値で、ふつうは記事のパーマリンク(絶対URL)をそのまま使えます。
ひとつだけ、絶対に飛ばしてほしくないルールがあります。タイトルやdescriptionに & や < が入ったら、必ずエスケープする。 冒頭の事故はこれを怠っただけで起きました。R&D も A<B も、生のままXMLに入れると即死します。
依存ゼロで動く、RSS 2.0ジェネレーター
まずはフレームワークに依存しないNode.js版です。scripts/generate-rss.mjs として保存し、node scripts/generate-rss.mjs で dist/rss.xml を吐き出します。記事データをCMSやMDXから読む部分は、自分のプロジェクトに合わせて差し替えてください。
// scripts/generate-rss.mjs
import fs from "node:fs";
import path from "node:path";
const siteUrl = "https://example.com";
const outputPath = path.join(process.cwd(), "dist", "rss.xml");
// 本来はCMSやMDXから読み込む。ここではサンプルとして直書き
const posts = [
{
title: "Claude CodeでRSSフィードを実装する",
description: "RSS 2.0フィードを安全に生成し、検証する方法を解説します。",
slug: "claude-code-rss-feed",
pubDate: "2026-06-06T09:00:00+09:00",
tags: ["Claude Code", "RSS"],
},
{
title: "Claude Codeでサイトマップを生成する",
description: "検索エンジン向けにsitemap.xmlを生成する実装例です。",
slug: "claude-code-sitemap-generation",
pubDate: "2026-05-30T09:00:00+09:00",
tags: ["Claude Code", "SEO"],
},
];
// 門番その1:XMLを壊す5文字を必ず変換する
function escapeXml(value) {
return String(value ?? "")
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
// 門番その2:相対パスを絶対URLに直す
function absoluteUrl(pathname) {
return new URL(pathname, siteUrl).toString();
}
// 門番その3:おかしな日付はその場で例外にして止める
function toRssDate(value) {
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
throw new Error(`日付が不正です: ${value}`);
}
return date.toUTCString(); // RSSはRFC 822系(toUTCString)に寄せる
}
const items = posts
.sort((a, b) => new Date(b.pubDate).getTime() - new Date(a.pubDate).getTime())
.map((post) => {
const url = absoluteUrl(`/blog/${post.slug}/`);
const categories = post.tags
.map((tag) => ` <category>${escapeXml(tag)}</category>`)
.join("\n");
return ` <item>
<title>${escapeXml(post.title)}</title>
<link>${url}</link>
<guid isPermaLink="true">${url}</guid>
<description>${escapeXml(post.description)}</description>
<pubDate>${toRssDate(post.pubDate)}</pubDate>
${categories}
</item>`;
})
.join("\n");
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>ClaudeCodeLab</title>
<link>${siteUrl}/</link>
<description>Claude Codeの実装ガイドと運用ノウハウ</description>
<language>ja</language>
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
<ttl>60</ttl>
${items}
</channel>
</rss>
`;
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, xml, "utf8");
console.log(`生成しました: ${outputPath}`);
このコードの心臓部は、コメントで「門番」と書いた3つの関数です。escapeXml がXMLを壊す文字を変換し、absoluteUrl が相対パスを絶対URLに直し、toRssDate が不正な日付を例外で止める。Claude Codeにレビューさせるときは、「この3つの門番だけは絶対に消すな」 と明記します。便利そうに見えて、ここを省略した瞬間に冒頭の事故が再発します。
Astroなら、公式の @astrojs/rss から始める
Astroサイトなら、公式パッケージの @astrojs/rss を使うのがいちばん安全です。XMLの組み立てやエスケープをパッケージ側が面倒みてくれるので、自分で文字列を連結するより事故りにくい。astro.config.mjs に site が設定されているのを確認してから、src/pages/rss.xml.ts を作ります。
npm install @astrojs/rss
// src/pages/rss.xml.ts
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
export async function GET(context: { site: URL }) {
// draftを除外する。ここを忘れると下書きが全世界に配信される
const posts = await getCollection("blog", ({ data }) => !data.draft);
const items = posts
.sort((a, b) => {
const aDate = new Date(a.data.updatedDate ?? a.data.pubDate).getTime();
const bDate = new Date(b.data.updatedDate ?? b.data.pubDate).getTime();
return bDate - aDate; // 新しい順
})
.slice(0, 30) // 件数を絞る
.map((post) => ({
title: post.data.title,
description: post.data.description,
pubDate: post.data.updatedDate ?? post.data.pubDate,
link: `/blog/${post.id}/`,
categories: post.data.tags,
}));
return rss({
title: "ClaudeCodeLab",
description: "Claude Codeの実装ガイドと運用ノウハウ",
site: context.site, // ここが絶対URLの起点になる
items,
customData: "<language>ja</language><ttl>60</ttl>",
});
}
link は相対パスで渡していますが、site を起点に @astrojs/rss が絶対URLへ展開してくれます。だから astro.config.mjs の site が未設定だと、ここで一気に壊れます。最初に確認すべきはこの1点です。
全文HTMLをRSSに入れたくなったら、MarkdownをHTML化してからsanitizeする必要があります。sanitizeは、MDX由来のHTMLから危険なタグや属性を落とす処理のこと。ただ最初から全文配信にすると、相対パスの画像・広告スクリプト・埋め込みコンポーネントがフィード上で軒並み壊れます。最初はdescription配信から始めるのが現実的です。
HTMLの head には自動検出用のリンクを入れておきます。これがあると、ブラウザ拡張やリーダーがフィードを見つけてくれます。
<link rel="alternate" type="application/rss+xml" title="ClaudeCodeLab RSS" href="/rss.xml" />
<link rel="alternate" type="application/atom+xml" title="ClaudeCodeLab Atom" href="/atom.xml" />
Atomと多言語フィードで気をつけること
Atomを出すなら、feed・entry・id・updated を安定させます。Atomの日付はISO 8601形式なので、JavaScriptなら toISOString() がそのまま使えて楽です。
// Atom用のentryを1件分組み立てる
function atomEntry(post, siteUrl) {
const url = new URL(`/blog/${post.slug}/`, siteUrl).toString();
const updated = new Date(post.pubDate).toISOString(); // AtomはISO 8601
return ` <entry>
<title>${escapeXml(post.title)}</title>
<link href="${url}" />
<id>${url}</id>
<updated>${updated}</updated>
<summary>${escapeXml(post.description)}</summary>
</entry>`;
}
多言語サイトでは、フィード生成をロケール設定で分けます。コレクション名・URL prefix・language を1つの表で対応させておくと、取り違えが減ります。
// 言語ごとに collection / prefix / language を一致させる
const feeds = [
{ collection: "blog", prefix: "", language: "ja", title: "ClaudeCodeLab" },
{ collection: "blog-en", prefix: "/en", language: "en", title: "ClaudeCodeLab English" },
{ collection: "blog-zh", prefix: "/zh", language: "zh", title: "ClaudeCodeLab 中文" },
];
ここでいちばん多い失敗が、日本語記事のURLを英語フィードに混ぜてしまうことです。タイトルだけ翻訳されていても、リンク先が日本語記事だと読者体験は台無しになります。Claude Codeに頼むときは「各localeで collection・prefix・language が一致しているか」をレビュー条件に必ず入れます。
検証スクリプトで、壊れたフィードを公開前に止める
生成したら、ブラウザでXMLが見えるだけでは不十分です。W3C Feed Validatorで公開URLを検証し、ローカルでも最低限の構造を機械チェックします。次のスクリプトはNode.js 18以上で動きます(fetch が標準で使えます)。
// scripts/check-feed.mjs
const feedUrl = process.argv[2] ?? "http://localhost:4321/rss.xml";
const response = await fetch(feedUrl);
const xml = await response.text();
const failures = [];
if (!response.ok) failures.push(`HTTPステータスが ${response.status}`);
if (!xml.includes("<rss")) failures.push("rss要素がない");
if (!xml.includes("<channel>")) failures.push("channel要素がない");
if (!xml.includes("<item>")) failures.push("item要素がない");
// エスケープされていない & を検出する(冒頭の事故対策)
if (/&(?!amp;|lt;|gt;|quot;|apos;|#\d+;|#x[a-fA-F0-9]+;)/.test(xml)) {
failures.push("未エスケープの & がXMLを壊す可能性あり");
}
if (!/<guid[^>]*>https?:\/\//.test(xml)) failures.push("guidが絶対URLでない");
if (!/<link>https?:\/\//.test(xml)) failures.push("channelまたはitemのlinkが絶対URLでない");
if (failures.length) {
console.error(failures.map((failure) => `- ${failure}`).join("\n"));
process.exit(1); // CIで落とせるように非ゼロで終了
}
console.log(`OK: ${feedUrl} はRSSフィードとして問題なさそう`);
このスクリプトのいいところは、process.exit(1) でCIを落とせる点です。npm run build のあとにこれを走らせるようにしておけば、未エスケープの & や相対URLが混じったフィードは、デプロイ前に弾かれます。冒頭の「2週間気づかなかった事故」は、この5行があれば即日で止まっていました。
キャッシュは強すぎても弱すぎても困ります。静的ホスティングではCDNがXMLを配るので、更新が数分遅れることがある。記事を頻繁に出すサイトなら Cache-Control: public, max-age=300, s-maxage=3600 のように、ブラウザは短め・CDNは少し長めにする設計が扱いやすいです。完全な静的生成だとヘッダー設定はホスティング側になるので、Netlify・Vercel・Cloudflare Pagesの設定ファイルも忘れず確認します。
Claude Codeに渡すプロンプトと、レビューの観点
Claude Codeには、実装とレビューを分けて頼むと安定します。最初の依頼ではファイル範囲と仕様をガチガチに固定し、次の依頼で生成結果を批判的に見てもらう。この2段構えが効きます。
Astroの静的サイトにRSS 2.0フィードを実装してください。
制約:
- src/pages/rss.xml.ts だけを新規作成または編集する
- @astrojs/rss を使う
- draft記事は除外する
- link と guid は絶対URLになる設計にする
- pubDate または updatedDate で新しい順に並べる
- 最大30件に制限する
- customData に language と ttl を入れる
確認:
- RSS Advisory Boardの仕様に照らして channel と item の必須情報を確認する
- W3C Feed Validatorで検証できる形にする
- XMLエスケープ・日付・URL・多言語展開の注意点を、コメントではなく実装で守る
- 最後に実行したコマンドと確認結果を報告する
レビュー用の指示は、もっと厳しくします。褒めさせるのではなく、壊れている箇所を探させるのが目的です。
実装済みのRSSフィードをレビューしてください。
バグ・仕様違反・読者体験の問題・SEOの問題・キャッシュの問題を優先して指摘してください。
特に、相対URL・draft混入・XMLエスケープ漏れ・日付形式・guidの安定性・多言語URLの不一致を確認してください。
問題がなければ、残るリスクと手動確認項目だけを短くまとめてください。
Claude Codeはコード生成だけでなく、公開前の品質レビューにも使えます。ただし最終判断は人間が持つべきです。仕様リンクを読んだか、公開URLで検証したか、実際のRSSリーダーで開いて見たか。この3点だけは自分の目で確認してください。
よくある質問
Q. RSSとAtom、結局どっちを作ればいい? 最初はRSS 2.0だけで十分です。対応リーダーが多く、実装も単純。社内システムや特定のクライアントがAtomを要求する場合だけ、あとから足せば間に合います。両方出すなら、それぞれをW3C Feed Validatorで別々に検証してください。
Q. ブラウザでXMLがちゃんと表示されているのに、リーダーで読めないのはなぜ?
ブラウザは多少壊れたXMLでも整形して見せてしまうからです。未エスケープの &、相対URL、不正な日付は、ブラウザでは見えてもリーダーでは弾かれます。必ず検証スクリプトかW3C Feed Validatorを通してください。
Q. 全文をRSSに載せたい。最初からやって大丈夫? おすすめしません。MarkdownをHTML化してsanitizeする処理が必要で、相対パスの画像・広告スクリプト・埋め込みコンポーネントが壊れやすい。まずはdescription配信で安定させてから、全文配信に移行するのが安全です。
Q. 多言語サイトでフィードが混ざります。どう防ぐ? collection・URL prefix・language を1つの表で対応づけ、それをそのままコードに落とすことです。日本語記事のリンクが英語フィードに紛れ込むのが典型的な事故なので、Claude Codeのレビュー条件に「各localeの3項目の一致」を必ず入れてください。
Q. 更新したのにリーダーに反映されません。
CDNのキャッシュが残っているか、updatedDate で並べ替えたのに pubDate が古いままで、リーダーが「更新なし」と判断している可能性があります。Cache-Control の設定と、並べ替えに使う日付フィールドを見直してください。
実際に試した結果
冒頭の「2週間誰も気づかなかった事故」以来、僕はRSSを「作るもの」ではなく「壊れていないか見張り続けるもの」だと考えを切り替えました。
実際に小さなAstro構成で検証してみると、いちばん多かった失敗はやっぱりXMLエスケープ漏れと相対URLでした。R&D を含むタイトル、?ref=blog&lang=ja を含むURL、タイムゾーン付きの日付。この3つを混ぜると、目視の確認ではほぼ見落とします。
効いたのは派手な対策ではなく、地味な検証スクリプトでした。scripts/check-feed.mjs をビルド後に走らせて、未エスケープの & と相対URLでCIを落とすようにしただけで、壊れたフィードが公開前に止まるようになった。W3C Feed Validatorと組み合わせれば、公開前にほとんどの問題を拾えます。賢い生成より、転んでも公開されない門番を1つ置く。RSSに関しては、これがいちばん効く、というのが今の実感です。
こうしたフィード・サイトマップ・記事品質チェック・CTA設計をまとめて整えたい方は、Claude Code研修・導入相談でも扱っています。記事を増やすだけでなく、公開後にちゃんと読者へ届く仕組みまで作るのが目的です。実装の元になる記事データの管理は、Claude CodeでブログCMSを作るから読み進めると流れがつながります。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
まず無料PDFで基本を固め、繰り返し使う作業はGumroad教材へ、チーム導入や権限設計は導入相談へ進めます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
制作会社がClaude Codeに触らせる前に決める権限チェックリスト
クライアントサイトを壊さずにAI編集を使うための、制作会社向け権限と確認の型です。
SaaSサポートのバグ報告をClaude Codeで再現手順に変える実務フロー
問い合わせ文をそのまま開発へ投げず、再現手順、証拠、次の一手に整えるサポート向け手順です。
Obsidianの古いメモをClaude Codeの指示書に変える10分ルーチン
Obsidianに溜めたメモが毎回ゴミになる人へ。事実・決定・未確認に仕分けして、Claude Codeがそのまま動ける指示書に変える朝の10分の型を紹介します。