From 0e409e37d9774190b9b44bd89249be817df40ce8 Mon Sep 17 00:00:00 2001
From: anthonyrawlins
Date: Thu, 28 Aug 2025 02:16:26 +1000
Subject: [PATCH] Add auto-generated RSS feed with scheduled publishing support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 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 tags
✅ Graceful error handling with fallback feed
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude
---
app/layout.tsx | 5 +++
app/rss.xml/route.ts | 101 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 106 insertions(+)
create mode 100644 app/rss.xml/route.ts
diff --git a/app/layout.tsx b/app/layout.tsx
index 49a2624..27d5d7f 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -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',
diff --git a/app/rss.xml/route.ts b/app/rss.xml/route.ts
new file mode 100644
index 0000000..3eb6867
--- /dev/null
+++ b/app/rss.xml/route.ts
@@ -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, ''')
+}
+
+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 = `
+
+
+ ${escapeXml(SITE_TITLE)}
+ ${SITE_URL}
+ ${escapeXml(SITE_DESCRIPTION)}
+ ${SITE_LANGUAGE}
+ ${AUTHOR_EMAIL} (${AUTHOR_NAME})
+ ${AUTHOR_EMAIL} (${AUTHOR_NAME})
+ ${formatRssDate(new Date().toISOString())}
+
+ CHORUS Blog RSS Generator
+ 60
+${posts.map(post => ` -
+ ${escapeXml(post.title)}
+ ${SITE_URL}/posts/${post.slug}
+ ${escapeXml(post.description)}
+ ${AUTHOR_EMAIL} (${post.author.name})
+ ${post.tags.map(tag => escapeXml(tag)).join(', ')}
+ ${SITE_URL}/posts/${post.slug}
+ ${formatRssDate(post.date)}
+ Author: ${escapeXml(post.author.name)}${post.author.role ? `, ${escapeXml(post.author.role)}` : ''}
+ Reading Time: ${post.readingTime} min
+ Tags: ${post.tags.map(tag => escapeXml(tag)).join(', ')}
+
+ ${post.content}
+
+ Read full post on CHORUS Blog
+ ]]>
+ `).join('\n')}
+
+`
+
+ 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 = `
+
+
+ ${escapeXml(SITE_TITLE)} - Error
+ ${SITE_URL}
+ Error generating RSS feed
+ ${SITE_LANGUAGE}
+ ${formatRssDate(new Date().toISOString())}
+ -
+ RSS Feed Error
+ ${SITE_URL}
+ There was an error generating the RSS feed. Please try again later.
+ ${formatRssDate(new Date().toISOString())}
+
+
+`
+
+ return new NextResponse(errorRss, {
+ status: 500,
+ headers: {
+ 'Content-Type': 'application/rss+xml; charset=utf-8',
+ },
+ })
+ }
+}
\ No newline at end of file