/**
* 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' },
});
}