|
| 1 | +import { createAPIFileRoute } from '@tanstack/react-start/api' |
| 2 | +import { getPublishedPosts, formatAuthors } from '~/utils/blog' |
| 3 | + |
| 4 | +function escapeXml(unsafe: string): string { |
| 5 | + return unsafe |
| 6 | + .replace(/&/g, '&') |
| 7 | + .replace(/</g, '<') |
| 8 | + .replace(/>/g, '>') |
| 9 | + .replace(/"/g, '"') |
| 10 | + .replace(/'/g, ''') |
| 11 | +} |
| 12 | + |
| 13 | +function generateRSSFeed() { |
| 14 | + const posts = getPublishedPosts().slice(0, 50) // Most recent 50 posts |
| 15 | + const siteUrl = 'https://tanstack.com' |
| 16 | + const buildDate = new Date().toUTCString() |
| 17 | + |
| 18 | + const rssItems = posts |
| 19 | + .map((post) => { |
| 20 | + const postUrl = `${siteUrl}/blog/${post.slug}` |
| 21 | + const pubDate = new Date(post.published).toUTCString() |
| 22 | + const author = formatAuthors(post.authors) |
| 23 | + |
| 24 | + // Use excerpt if available, otherwise try to get first paragraph from content |
| 25 | + let description = post.excerpt || '' |
| 26 | + if (!description && post.content) { |
| 27 | + // Extract first paragraph after frontmatter |
| 28 | + const contentWithoutFrontmatter = post.content.replace(/^---[\s\S]*?---/, '').trim() |
| 29 | + const firstParagraph = contentWithoutFrontmatter.split('\n\n')[0] |
| 30 | + description = firstParagraph.replace(/!\[[^\]]*\]\([^)]*\)/g, '') // Remove images |
| 31 | + } |
| 32 | + |
| 33 | + return ` |
| 34 | + <item> |
| 35 | + <title>${escapeXml(post.title)}</title> |
| 36 | + <link>${escapeXml(postUrl)}</link> |
| 37 | + <guid isPermaLink="true">${escapeXml(postUrl)}</guid> |
| 38 | + <pubDate>${pubDate}</pubDate> |
| 39 | + <author>${escapeXml(author)}</author> |
| 40 | + <description>${escapeXml(description)}</description> |
| 41 | + ${post.headerImage ? `<enclosure url="${escapeXml(siteUrl + post.headerImage)}" type="image/png" />` : ''} |
| 42 | + </item>` |
| 43 | + }) |
| 44 | + .join('') |
| 45 | + |
| 46 | + return `<?xml version="1.0" encoding="UTF-8"?> |
| 47 | +<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> |
| 48 | + <channel> |
| 49 | + <title>TanStack Blog</title> |
| 50 | + <link>${siteUrl}/blog</link> |
| 51 | + <description>The latest news and updates from TanStack</description> |
| 52 | + <language>en-us</language> |
| 53 | + <lastBuildDate>${buildDate}</lastBuildDate> |
| 54 | + <atom:link href="${siteUrl}/rss.xml" rel="self" type="application/rss+xml" /> |
| 55 | + ${rssItems} |
| 56 | + </channel> |
| 57 | +</rss>` |
| 58 | +} |
| 59 | + |
| 60 | +export const APIRoute = createAPIFileRoute('/rss.xml')({ |
| 61 | + GET: async () => { |
| 62 | + const rss = generateRSSFeed() |
| 63 | + |
| 64 | + return new Response(rss, { |
| 65 | + headers: { |
| 66 | + 'Content-Type': 'application/xml; charset=utf-8', |
| 67 | + 'Cache-Control': 'public, max-age=300, s-maxage=3600', |
| 68 | + }, |
| 69 | + }) |
| 70 | + }, |
| 71 | +}) |
0 commit comments