Implement RSS Feeds with Claude Code: Static Site Guide for RSS 2.0 and Atom
Build RSS 2.0 and Atom feeds with Claude Code, safe XML escaping, dates, absolute URLs, validation, and caching.
RSS is still a practical distribution channel
Publishing a blog post is not the same as delivering it to readers. Search, newsletters, social feeds, Slack bots, and RSS readers all compete for attention. RSS is the quiet channel that keeps working even when a social post disappears in a timeline. For technical readers, researchers, editors, and internal teams, it is still a dependable way to follow updates.
RSS is an XML file that lists article metadata: title, URL, description, publication date, and categories. XML is a strict text format. A single unescaped & can break the feed. Atom is a related feed format standardized as RFC 4287. For most static sites, start with RSS 2.0 and add Atom only when your audience or integrations need it.
This guide shows a beginner-friendly workflow for building feeds with Claude Code without treating the result as magic. We will cover RSS 2.0, Atom, safe escaping, date formats, absolute URLs, multilingual feeds, validation, cache headers, and review prompts. For adjacent site operations, read Blog CMS with Claude Code, SEO optimization with Claude Code, and sitemap generation.
Use primary sources for the final checks: the RSS Advisory Board RSS 2.0 Specification, IETF RFC 4287 for Atom, the W3C Feed Validation Service, and the Astro RSS recipe.
Three real use cases
The first use case is repeat readership for a developer blog. RSS readers may look niche, but they are common among people who read many technical sources. If your Claude Code article appears in Feedly or Inoreader, the reader does not need to remember your domain or catch a social post at the right hour.
The second use case is internal knowledge distribution. Release notes, incident reports, security notices, and architecture decision records can be exposed as a feed. A Slack bot, a portal, or a status dashboard can consume the same feed that humans read on the website. One content source serves both people and machines.
The third use case is multilingual publishing. If a site has Japanese, English, Chinese, Korean, Spanish, French, German, Portuguese, Hindi, and Indonesian collections, one global feed is not enough. /rss.xml, /en/rss.xml, and /zh/rss.xml should point to the right language, collection, and URL prefix.
There is also a monetization use case. On ClaudeCodeLab, a feed is part of the path from article to template, product, training, or consultation. A useful implementation article that never reaches repeat readers cannot support revenue. Connect the feed to a practical next step such as Claude Code training and consultation, but keep the CTA helpful rather than pushy.
Decide the feed contract before coding
Before asking Claude Code to implement anything, write the contract. Most broken feeds come from vague requirements: relative URLs, drafts included by mistake, invalid XML, mixed locales, or dates that readers sort incorrectly.
| Area | Recommended choice | Why it matters |
|---|---|---|
| Format | RSS 2.0 first, Atom optional | RSS has broad reader support and simple structure |
| URLs | Absolute URLs | External feed readers cannot reliably resolve site-relative links |
| Dates | toUTCString() for RSS, toISOString() for Atom | Keeps output close to expected feed formats |
| Body | Description first | Full HTML requires sanitizing and image URL rewriting |
| Limit | 20 to 50 items | Keeps the feed fast and reviewable |
| Cache | Explicit cache headers | Avoids stale feeds after publishing |
| Validation | W3C validator plus local checks | Browser display is not enough |
RSS 2.0 uses a channel for site metadata and item elements for entries. A guid should identify the item permanently; the article permalink is usually fine. Titles and descriptions must be escaped before they enter XML. Claude Code should not be allowed to delete this guardrail during refactoring.
A copy-paste RSS generator with no dependencies
Save this as scripts/generate-rss.mjs and run node scripts/generate-rss.mjs. Replace the sample posts array with your MDX, CMS, or database records.
// 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");
const posts = [
{
title: "Implement RSS feeds with Claude Code",
description: "Generate and validate an RSS 2.0 feed for a static site.",
slug: "claude-code-rss-feed",
pubDate: "2026-06-02T09:00:00+09:00",
tags: ["Claude Code", "RSS"],
},
{
title: "Generate a sitemap with Claude Code",
description: "Create sitemap.xml for search engines from the same content source.",
slug: "claude-code-sitemap-generation",
pubDate: "2026-05-30T09:00:00+09:00",
tags: ["Claude Code", "SEO"],
},
];
function escapeXml(value) {
return String(value ?? "")
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function absoluteUrl(pathname) {
return new URL(pathname, siteUrl).toString();
}
function toRssDate(value) {
const date = new Date(value);
if (Number.isNaN(date.getTime())) throw new Error(`Invalid date: ${value}`);
return date.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>Practical Claude Code implementation guides</description>
<language>en</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(`Generated ${outputPath}`);
The important pieces are small: escapeXml(), new URL(), and explicit date validation. If Claude Code rewrites the script, ask it to preserve those three behaviors and add tests before changing output shape.
Astro implementation with @astrojs/rss
For Astro, the official package handles the RSS wrapper. Confirm that site is set in astro.config.mjs, then add the package and create 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 }) {
const posts = await getCollection("blog-en", ({ 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: `/en/blog/${post.id}/`,
categories: post.data.tags,
}));
return rss({
title: "ClaudeCodeLab English",
description: "Practical Claude Code implementation guides",
site: context.site,
items,
customData: "<language>en</language><ttl>60</ttl>",
});
}
Add feed discovery links to your layout so readers and browser extensions can find the feed.
<link rel="alternate" type="application/rss+xml" title="ClaudeCodeLab RSS" href="/en/rss.xml" />
<link rel="alternate" type="application/atom+xml" title="ClaudeCodeLab Atom" href="/en/atom.xml" />
Full HTML feeds are possible, but they require sanitizing, absolute image URLs, and careful removal of interactive components. A description-only feed is the safer first version.
Atom, multilingual feeds, and validation
Atom is useful when an integration expects it. Use stable id values and ISO dates.
function atomEntry(post, siteUrl) {
const url = new URL(`/en/blog/${post.slug}/`, siteUrl).toString();
const updated = new Date(post.pubDate).toISOString();
return ` <entry>
<title>${escapeXml(post.title)}</title>
<link href="${url}" />
<id>${url}</id>
<updated>${updated}</updated>
<summary>${escapeXml(post.description)}</summary>
</entry>`;
}
For multilingual sites, keep collection, prefix, and language together:
const feeds = [
{ collection: "blog", prefix: "", language: "ja", title: "ClaudeCodeLab" },
{ collection: "blog-en", prefix: "/en", language: "en", title: "ClaudeCodeLab English" },
{ collection: "blog-es", prefix: "/es", language: "es", title: "ClaudeCodeLab Español" },
];
Validate locally before using the W3C service:
// scripts/check-feed.mjs
const feedUrl = process.argv[2] ?? "http://localhost:4321/en/rss.xml";
const response = await fetch(feedUrl);
const xml = await response.text();
const failures = [];
if (!response.ok) failures.push(`HTTP status is ${response.status}`);
if (!xml.includes("<rss")) failures.push("RSS root element is missing");
if (!xml.includes("<channel>")) failures.push("channel element is missing");
if (!xml.includes("<item>")) failures.push("item element is missing");
if (/&(?!amp;|lt;|gt;|quot;|apos;|#\d+;|#x[a-fA-F0-9]+;)/.test(xml)) failures.push("unescaped ampersand found");
if (!/<guid[^>]*>https?:\/\//.test(xml)) failures.push("guid should be absolute");
if (failures.length) {
console.error(failures.map((failure) => `- ${failure}`).join("\n"));
process.exit(1);
}
console.log(`OK: ${feedUrl} looks like an RSS feed`);
Common failures are concrete: R&D breaks XML when not escaped, drafts leak into the feed, pubDate sorts differently across readers, relative URLs fail in external apps, and a Spanish feed accidentally links to English pages. Cache can also hide fixes. For static hosting, configure the platform or CDN so RSS updates within a predictable window.
Claude Code prompt and final check
Use this implementation prompt:
Implement RSS 2.0 for this Astro static site.
- Edit only src/pages/rss.xml.ts.
- Use @astrojs/rss.
- Exclude drafts.
- Sort by updatedDate or pubDate, newest first.
- Limit to 30 items.
- Use absolute URLs through the Astro site config.
- Add language and ttl custom data.
- Report commands run and validation results.
Then run a review prompt:
Review the feed implementation critically.
Prioritize bugs in XML escaping, relative URLs, draft leakage, date format, guid stability, multilingual URL prefixes, validation, and cache behavior.
If there are no blockers, list remaining manual checks only.
I tested the workflow on a small Astro-style data set. The local script caught unescaped ampersands and missing absolute GUIDs before the feed reached a browser. The remaining human checks were translation quality, CTA relevance, and whether the feed appeared correctly in a real reader. For teams that want this folded into content operations, the next step is a Claude Code training and consultation review of CMS, sitemap, RSS, QA gates, and monetization paths.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Obsidian to CLAUDE.md Workflow: Stop Re-explaining Context
Turn Obsidian working notes into concise CLAUDE.md operating notes that make Claude Code sessions easier to resume.
Claude Code Revenue CTA Routing: Send Articles to PDF, Gumroad, and Consultation
A Claude Code workflow for routing article readers to the free PDF, Gumroad products, or consultation by intent.
Claude Code Team Handoff Rules: Review Evidence, Permissions, Rollback, and Revenue Paths
A practical Claude Code handoff format for team review, proof, permission rules, rollback, free PDF, Gumroad, and consultation paths.
Related Products
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.