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