Claude Code로 RSS Feed 구현하기: 정적 사이트 RSS 2.0과 Atom 실전 가이드
Claude Code로 RSS 2.0/Atom을 만들고 XML 이스케이프, 날짜, 절대 URL, 다국어, 검증, 캐시를 정리합니다.
RSS는 오래된 기능이 아니라 안정적인 배포 통로입니다
블로그 글을 공개했다고 독자가 바로 보는 것은 아닙니다. 검색, SNS, 뉴스레터, Slack, RSS 리더가 모두 입구가 됩니다. RSS feed는 글 제목, 링크, 설명, 날짜를 XML 형식으로 제공하는 파일입니다. XML은 규칙이 엄격해서 & 하나를 잘못 넣어도 feed가 깨질 수 있습니다.
Atom은 RSS와 비슷한 구독 형식이며 RFC 4287로 정의되어 있습니다. 대부분의 정적 사이트는 RSS 2.0부터 구현하고, 필요한 경우 Atom을 추가하면 충분합니다. 이 글은 Claude Code로 RSS를 만들 때 XML 이스케이프, 날짜, 절대 URL, 다국어 feed, 검증, 캐시, review prompt까지 함께 다룹니다.
기준 문서는 RSS Advisory Board RSS 2.0 Specification, IETF RFC 4287, W3C Feed Validation Service, Astro RSS recipe입니다. 관련 글로는 Claude Code 블로그 CMS, SEO 최적화, 사이트맵 생성을 함께 보면 좋습니다.
실제 사용 사례
첫 번째는 개발 블로그의 재방문입니다. RSS 리더를 쓰는 독자는 소수처럼 보이지만, 기술 문서를 많이 읽는 사람에게는 여전히 일상 도구입니다. Feedly나 Inoreader에 글이 들어가면 SNS 노출에 의존하지 않아도 됩니다.
두 번째는 사내 지식 배포입니다. 릴리스 노트, 장애 회고, 보안 공지, 아키텍처 결정 기록을 같은 feed로 제공하면 Slack bot이나 사내 포털이 읽을 수 있습니다. 사람이 보는 페이지와 기계가 읽는 XML을 같은 데이터에서 만들 수 있습니다.
세 번째는 다국어 SEO입니다. 한국어, 일본어, 영어, 중국어, 스페인어 글이 있는 사이트라면 하나의 feed에 모든 언어를 섞지 않는 것이 좋습니다. /ko/rss.xml은 한국어 collection과 /ko/blog/ URL만 사용해야 합니다.
네 번째는 수익 경로 유지입니다. ClaudeCodeLab처럼 교육, 템플릿, 상담으로 이어지는 사이트에서는 새 글이 feed에 들어가야 반복 독자에게 도달합니다. Claude Code 교육과 상담 CTA도 독자가 구현 후 다음 단계로 갈 때 자연스럽게 보여야 합니다.
구현 전에 정할 계약
Claude Code에 맡기기 전에 feed 계약을 써야 합니다.
| 항목 | 권장 | 이유 |
|---|---|---|
| 형식 | RSS 2.0 먼저, Atom은 선택 | 지원이 넓고 단순함 |
| URL | 절대 URL | 외부 리더가 안정적으로 열 수 있음 |
| 날짜 | RSS는 toUTCString(), Atom은 toISOString() | reader의 정렬 오류를 줄임 |
| 본문 | description 중심 | 전체 HTML은 sanitize와 이미지 URL 처리가 필요 |
| 개수 | 20~50개 | feed가 너무 커지는 것을 방지 |
| 캐시 | 명시적 cache header | 발행 후 업데이트 지연을 관리 |
| 검증 | 로컬 스크립트와 W3C Validator | 브라우저 표시만으로는 부족 |
RSS 2.0의 기본은 channel과 item입니다. guid는 글을 안정적으로 식별하는 값이며 보통 permalink를 씁니다. 제목과 설명은 XML에 들어가기 전에 반드시 이스케이프해야 합니다.
의존성 없는 RSS 생성 코드
scripts/generate-rss.mjs로 저장하고 node scripts/generate-rss.mjs를 실행합니다.
// 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: "Claude Code로 RSS Feed 구현하기",
description: "정적 사이트용 RSS 2.0 feed를 생성하고 검증합니다.",
slug: "claude-code-rss-feed",
pubDate: "2026-06-02T09:00:00+09:00",
tags: ["Claude Code", "RSS"],
},
];
function escapeXml(value) {
return String(value ?? "")
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
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.map((post) => {
const url = new URL(`/ko/blog/${post.slug}/`, siteUrl).toString();
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>
</item>`;
}).join("\n");
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>ClaudeCodeLab Korea</title>
<link>${siteUrl}/ko/</link>
<description>Claude Code 실전 구현 가이드</description>
<language>ko</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}`);
핵심은 escapeXml(), new URL(), 날짜 검증입니다. Claude Code가 코드를 줄이더라도 이 세 가지는 유지해야 합니다.
Astro에서 구현하기
Astro에서는 공식 @astrojs/rss를 사용합니다.
npm install @astrojs/rss
// src/pages/ko/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-ko", ({ 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: `/ko/blog/${post.id}/`,
categories: post.data.tags,
}));
return rss({
title: "ClaudeCodeLab Korea",
description: "Claude Code 실전 구현 가이드",
site: context.site,
items,
customData: "<language>ko</language><ttl>60</ttl>",
});
}
레이아웃에는 자동 발견 링크를 넣습니다.
<link rel="alternate" type="application/rss+xml" title="ClaudeCodeLab RSS" href="/ko/rss.xml" />
<link rel="alternate" type="application/atom+xml" title="ClaudeCodeLab Atom" href="/ko/atom.xml" />
전체 HTML feed는 가능하지만 sanitize, 이미지 절대 URL, interactive component 제거가 필요합니다. 처음에는 description만 넣는 방식이 안전합니다.
Atom, 다국어, 검증
Atom은 stable id와 ISO date가 중요합니다.
function atomEntry(post, siteUrl) {
const url = new URL(`/ko/blog/${post.slug}/`, siteUrl).toString();
return ` <entry>
<title>${escapeXml(post.title)}</title>
<link href="${url}" />
<id>${url}</id>
<updated>${new Date(post.pubDate).toISOString()}</updated>
<summary>${escapeXml(post.description)}</summary>
</entry>`;
}
다국어 feed 설정은 collection, prefix, language를 함께 관리합니다.
const feeds = [
{ collection: "blog", prefix: "", language: "ja", title: "ClaudeCodeLab" },
{ collection: "blog-en", prefix: "/en", language: "en", title: "ClaudeCodeLab English" },
{ collection: "blog-ko", prefix: "/ko", language: "ko", title: "ClaudeCodeLab Korea" },
];
로컬 검증 스크립트도 추가합니다.
// scripts/check-feed.mjs
const feedUrl = process.argv[2] ?? "http://localhost:4321/ko/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("missing rss root");
if (!xml.includes("<channel>")) failures.push("missing channel");
if (!xml.includes("<item>")) failures.push("missing item");
if (/&(?!amp;|lt;|gt;|quot;|apos;|#\d+;|#x[a-fA-F0-9]+;)/.test(xml)) failures.push("unescaped ampersand");
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}`);
실패 사례는 구체적입니다. R&D 같은 문자열을 이스케이프하지 않아 XML이 깨지고, draft가 feed에 노출되고, 상대 URL이 외부 reader에서 열리지 않고, 한국어 feed가 영어 페이지로 연결됩니다. CDN 캐시가 너무 길면 수정 후에도 오래된 feed가 보입니다.
Claude Code prompt와 검증 결과
Astro 정적 사이트에 RSS 2.0을 구현하세요.
- src/pages/ko/rss.xml.ts만 수정합니다.
- @astrojs/rss를 사용합니다.
- draft는 제외합니다.
- updatedDate 또는 pubDate 기준 최신순으로 정렬합니다.
- 30개로 제한합니다.
- /ko/blog/ URL prefix를 유지합니다.
- language와 ttl을 넣습니다.
- 실행한 검증 명령과 결과를 보고합니다.
리뷰 prompt는 더 엄격하게 씁니다.
RSS 구현을 비판적으로 검토하세요.
XML 이스케이프, 상대 URL, draft 노출, 날짜 형식, guid 안정성, 다국어 prefix, 캐시, W3C 검증 문제를 우선 확인하세요.
작은 Astro 데이터셋으로 실제 확인했을 때, 로컬 스크립트가 이스케이프되지 않은 &와 절대 URL이 아닌 guid를 바로 잡아냈습니다. 사람이 봐야 할 부분은 번역 품질, CTA의 자연스러움, 실제 reader 표시였습니다. 팀에서 RSS, sitemap, 콘텐츠 QA, 수익 경로를 함께 운영하려면 Claude Code 교육과 상담에서 workflow를 정리하는 것이 좋습니다.
무료 PDF: Claude Code 치트시트
이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.
개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.
작성자 소개
Masa
Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.
관련 글
Obsidian 메모를 CLAUDE.md로 바꾸는 Claude Code 워크플로
Obsidian 작업 메모를 CLAUDE.md 운영 노트로 정리해 Claude Code 세션의 문맥 반복을 줄입니다.
Claude Code Revenue CTA Routing: 글에서 PDF, Gumroad, 상담으로 보내기
독자 의도에 따라 무료 PDF, Gumroad 상품, 상담으로 나누는 Claude Code CTA 설계입니다.
Claude Code 팀 인계 규칙: 리뷰 증거, 권한, 롤백, 수익 경로까지 넘기는 법
Claude Code 작업을 팀에 넘길 때 필요한 증거, 권한 규칙, 롤백, 무료 PDF, Gumroad, 상담 경로 체크리스트.