/** * Blog RSS feed — GET /blog/rss.xml * * Syndication for the store's articles (many of them auto-published by the * SEO Autopilot): feed readers, newsletter tools, and aggregators can follow * the blog without polling HTML. The root layout advertises it via * . * * NOTE (multi-locale scaffolds): the URL contains a dot, so the locale * middleware never rewrites it, and a copy nested under [locale]/blog/ would * 404. The scaffolder moves this route back to the app ROOT * (app/blog/rss.xml) after the [locale] move — the literal `blog` segment * wins over `[locale]` for /blog/rss.xml, while /he/blog/... keeps matching * the locale tree. See scaffold.ts. */ import { getServerClient } from '@/core/lib/brainerce.server'; import { getCanonicalSiteUrl } from '@/core/lib/site-url'; export const revalidate = 3600; function esc(value: string): string { return value .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } export async function GET() { const baseUrl = await getCanonicalSiteUrl(); const client = await getServerClient(); const [info, posts] = await Promise.all([ client.getStoreInfo().catch(() => null), client.blog.getPosts({ limit: 50 }).catch(() => null), ]); const items = (posts?.data ?? []) .map((post) => { const url = `${baseUrl}/blog/${post.slug}`; const description = post.seoDescription ?? post.excerpt ?? ''; const pubDate = post.publishedAt ? new Date(post.publishedAt).toUTCString() : undefined; return [ ' ', ` ${esc(post.title)}`, ` ${esc(url)}`, ` ${esc(url)}`, ...(pubDate ? [` ${pubDate}`] : []), ...(description ? [` ${esc(description)}`] : []), ' ', ].join('\n'); }) .join('\n'); const xml = [ '', '', ' ', ` ${esc(info?.name ?? 'Blog')}`, ` ${esc(`${baseUrl}/blog`)}`, ` ${esc(info?.metaDescription ?? `${info?.name ?? 'Store'} blog`)}`, ` `, items, ' ', '', ].join('\n'); return new Response(xml, { headers: { 'Content-Type': 'application/rss+xml; charset=utf-8' }, }); }