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>
This commit is contained in:
@@ -25,6 +25,11 @@ export const metadata: Metadata = {
|
||||
metadataBase: new URL('https://blog.chorus.services'),
|
||||
alternates: {
|
||||
canonical: 'https://blog.chorus.services',
|
||||
types: {
|
||||
'application/rss+xml': [
|
||||
{ url: '/rss.xml', title: 'CHORUS PING! RSS Feed' }
|
||||
]
|
||||
}
|
||||
},
|
||||
openGraph: {
|
||||
type: 'website',
|
||||
|
||||
101
app/rss.xml/route.ts
Normal file
101
app/rss.xml/route.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
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, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''')
|
||||
}
|
||||
|
||||
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',
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user