Files
chorus-ping-blog/app/rss.xml/route.ts
anthonyrawlins 0e409e37d9 Add auto-generated RSS feed with scheduled publishing support
- Created /rss.xml API route that generates valid RSS 2.0 XML
- RSS feed integrates with scheduled publishing system automatically
- Includes full post content, proper metadata, and CDATA encoding
- Added RSS feed link to HTML metadata for auto-discovery
- RSS link already exists in blog footer navigation
- 1-hour caching with error handling and fallback RSS
- Feed updates automatically when new posts go live

Features:
 Works with scheduled posts (future posts excluded)
 Proper XML escaping and CDATA content encoding
 Rich metadata including author, categories, publish dates
 Auto-discovery via HTML <link> tags
 Graceful error handling with fallback feed

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-28 02:16:26 +10:00

101 lines
3.7 KiB
TypeScript

import { getSortedPostsData } from '@/lib/blog'
import { NextResponse } from 'next/server'
// RSS feed configuration
const SITE_URL = 'https://blog.chorus.services'
const SITE_TITLE = 'CHORUS Services Blog'
const SITE_DESCRIPTION = 'Contextual AI orchestration, on-premises infrastructure, and the future of intelligent systems.'
const SITE_LANGUAGE = 'en-us'
const AUTHOR_EMAIL = 'noreply@chorus.services'
const AUTHOR_NAME = 'CHORUS Services'
function escapeXml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
}
function formatRssDate(dateString: string): string {
const date = new Date(dateString)
return date.toUTCString()
}
export async function GET() {
try {
// Get all published posts (includes scheduled posts that should be live)
const posts = getSortedPostsData()
// Generate RSS XML
const rssXml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>${escapeXml(SITE_TITLE)}</title>
<link>${SITE_URL}</link>
<description>${escapeXml(SITE_DESCRIPTION)}</description>
<language>${SITE_LANGUAGE}</language>
<managingEditor>${AUTHOR_EMAIL} (${AUTHOR_NAME})</managingEditor>
<webMaster>${AUTHOR_EMAIL} (${AUTHOR_NAME})</webMaster>
<lastBuildDate>${formatRssDate(new Date().toISOString())}</lastBuildDate>
<atom:link href="${SITE_URL}/rss.xml" rel="self" type="application/rss+xml" />
<generator>CHORUS Blog RSS Generator</generator>
<ttl>60</ttl>
${posts.map(post => ` <item>
<title>${escapeXml(post.title)}</title>
<link>${SITE_URL}/posts/${post.slug}</link>
<description>${escapeXml(post.description)}</description>
<author>${AUTHOR_EMAIL} (${post.author.name})</author>
<category>${post.tags.map(tag => escapeXml(tag)).join(', ')}</category>
<guid isPermaLink="true">${SITE_URL}/posts/${post.slug}</guid>
<pubDate>${formatRssDate(post.date)}</pubDate>
<content:encoded><![CDATA[
<p><strong>Author:</strong> ${escapeXml(post.author.name)}${post.author.role ? `, ${escapeXml(post.author.role)}` : ''}</p>
<p><strong>Reading Time:</strong> ${post.readingTime} min</p>
<p><strong>Tags:</strong> ${post.tags.map(tag => escapeXml(tag)).join(', ')}</p>
<hr>
<div>${post.content}</div>
<hr>
<p><a href="${SITE_URL}/posts/${post.slug}">Read full post on CHORUS Blog</a></p>
]]></content:encoded>
</item>`).join('\n')}
</channel>
</rss>`
return new NextResponse(rssXml, {
status: 200,
headers: {
'Content-Type': 'application/rss+xml; charset=utf-8',
'Cache-Control': 'public, max-age=3600, s-maxage=3600', // Cache for 1 hour
},
})
} catch (error) {
console.error('Error generating RSS feed:', error)
// Return a minimal RSS feed with error info
const errorRss = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>${escapeXml(SITE_TITLE)} - Error</title>
<link>${SITE_URL}</link>
<description>Error generating RSS feed</description>
<language>${SITE_LANGUAGE}</language>
<lastBuildDate>${formatRssDate(new Date().toISOString())}</lastBuildDate>
<item>
<title>RSS Feed Error</title>
<link>${SITE_URL}</link>
<description>There was an error generating the RSS feed. Please try again later.</description>
<pubDate>${formatRssDate(new Date().toISOString())}</pubDate>
</item>
</channel>
</rss>`
return new NextResponse(errorRss, {
status: 500,
headers: {
'Content-Type': 'application/rss+xml; charset=utf-8',
},
})
}
}