A new "Site URL" setting (validated, trailing-slash-normalized, with SITE_URL env fallback) anchors every absolute URL. On top of it: - /feed.xml — RSS 2.0 with the 20 newest published posts: excerpt description, full sanitized HTML in content:encoded (relative image and link URLs rewritten to absolute, since readers resolve nothing), categories from tags, dc:creator, and a self atom:link. Autodiscovery <link> on every public page and a footer link. - /sitemap.xml — home, post list, every published post and page (lastmod from updatedAt), and publicly visible tags. Rendered per request like the rest of the site so it never goes stale; drafts never appear. - /robots.txt — allow all, disallow /admin/ and /api/, sitemap pointer. Admin pages also carry noindex meta as a second layer. - Page metadata: metadataBase + canonical URLs everywhere, Open Graph (article type with published/modified times, author, and tags on posts; og:image + summary_large_image card when there's a featured image), and BlogPosting JSON-LD on post pages. Gotcha encoded in lib/seo.ts: Next merges metadata shallowly, so pages setting alternates.canonical alone would wipe the layout's RSS autodiscovery entry — pageAlternates() always sets both. Backups gain settings.siteUrl (export v4; older files still import). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
19 lines
529 B
TypeScript
19 lines
529 B
TypeScript
import type { MetadataRoute } from "next";
|
|
import { resolveSiteUrl } from "@/lib/seo";
|
|
import { getSettings } from "@/lib/services/settings";
|
|
|
|
// The sitemap URL depends on the admin-configured site URL setting.
|
|
export const dynamic = "force-dynamic";
|
|
|
|
export default async function robots(): Promise<MetadataRoute.Robots> {
|
|
const base = resolveSiteUrl(await getSettings());
|
|
return {
|
|
rules: {
|
|
userAgent: "*",
|
|
allow: "/",
|
|
disallow: ["/admin/", "/api/"],
|
|
},
|
|
sitemap: `${base}/sitemap.xml`,
|
|
};
|
|
}
|