/** * llms.txt — GET /llms.txt * * A machine-readable site summary for AI answer engines (ChatGPT, Perplexity, * Claude, AI Overviews): a concise markdown map of what the site is and where * its key content lives, so AI crawlers ground answers in (and cite) the * right pages. /agents.md is the richer agent-facing companion (the newer * convention) — keep the two consistent. */ import { getServerClient } from '@/core/lib/brainerce.server'; import { getCanonicalSiteUrl } from '@/core/lib/site-url'; export const revalidate = 3600; interface CategoryNodeLike { name: string; slug?: string | null; children?: CategoryNodeLike[]; } export async function GET() { const baseUrl = await getCanonicalSiteUrl(); const client = await getServerClient(); const [info, posts, categoriesRes] = await Promise.all([ client.getStoreInfo().catch(() => null), client.blog.getPosts({ limit: 20 }).catch(() => null), client.getCategories().catch(() => null), ]); const topCategories: CategoryNodeLike[] = ( (categoriesRes?.categories as CategoryNodeLike[] | undefined) ?? [] ).filter((c) => c.slug); const lines: string[] = [ `# ${info?.name ?? 'Store'}`, '', ...(info?.metaDescription ? [`> ${info.metaDescription}`, ''] : []), '## Key pages', '', `- [Products](${baseUrl}/products): full catalog`, ...topCategories .slice(0, 20) .map((c) => `- [${c.name}](${baseUrl}/category/${c.slug}): category`), `- [Blog](${baseUrl}/blog): guides and articles`, `- [FAQ](${baseUrl}/faq)`, `- [Contact](${baseUrl}/contact)`, ...(info?.contactEmail ? [`- Contact email: ${info.contactEmail}`] : []), '', '## Machine-readable surfaces', '', `- [Agent guide](${baseUrl}/agents.md)`, `- [Sitemap](${baseUrl}/sitemap.xml)`, `- [Blog RSS](${baseUrl}/blog/rss.xml)`, ]; if (posts && posts.data.length > 0) { lines.push('', '## Recent articles', ''); for (const post of posts.data) { const summary = post.seoDescription ?? post.excerpt ?? ''; lines.push(`- [${post.title}](${baseUrl}/blog/${post.slug})${summary ? `: ${summary}` : ''}`); } } return new Response(lines.join('\n') + '\n', { headers: { 'Content-Type': 'text/plain; charset=utf-8' }, }); }