feat(blog): add the blog index, post pages, RSS and content sitemap
The rendering split is dictated by CI building with no database. /blog, /blog/rss.xml and /content-sitemap.xml have no dynamic params, so Next prerenders them at build time and the build fails on a missing Payload secret — caught here, not on staging. They are force-dynamic instead: one indexed query against Postgres on the same Docker network, and a newly published post appears immediately rather than waiting on a revalidation. /blog/[slug] keeps ISR, because with no generateStaticParams there is nothing to prerender; it is generated on first request and cached, which is exactly what the collection's afterChange hook exists to invalidate. RichText takes `converters`, not `blocks`, in Payload 3.88, and the default converters must be spread or every paragraph and heading loses its renderer and the body comes out empty. BlogPosting references the Person and Organization by @id rather than repeating them, so every post and the About page resolve to one author entity instead of declaring several people with the same name. /sitemap.xml is proxied from FastAPI, which knows nothing about Payload, so the Next-owned URLs get their own sitemap and robots.txt lists both. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017YmbBhr8s7GusjDE12hrZM
This commit is contained in:
1 parent
21a5d18f59
commit
b793640507
15 files changed
+705
-14
No files matched your search
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* A second sitemap for the URLs Next owns.
|
||||
*
|
||||
* /sitemap.xml is proxied from FastAPI (app/(frontend)/sitemap.xml), which
|
||||
* knows nothing about Payload — the backend and frontend ship as separate
|
||||
* images. Rather than teach it, the Next-owned URLs get their own sitemap and
|
||||
* robots.txt lists both.
|
||||
*/
|
||||
import { getCachedPayload } from '@/lib/payload';
|
||||
import { absoluteUrl } from '@/lib/site';
|
||||
|
||||
/*
|
||||
* Dynamic, not ISR.
|
||||
*
|
||||
* This route has no dynamic params, so Next prerenders it at build time — and
|
||||
* CI builds the image with no database reachable, which fails the build. It is
|
||||
* a single indexed query against Postgres on the same Docker network, so
|
||||
* rendering per request is cheap, and it means a newly published post appears
|
||||
* here immediately rather than waiting on a revalidation.
|
||||
*/
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET() {
|
||||
const payload = await getCachedPayload();
|
||||
const { docs } = await payload.find({
|
||||
collection: 'posts',
|
||||
where: { _status: { equals: 'published' } },
|
||||
sort: '-publishedAt',
|
||||
limit: 500,
|
||||
depth: 0,
|
||||
});
|
||||
|
||||
const urls: Array<{ loc: string; lastmod: string | null }> = [
|
||||
{ loc: absoluteUrl('/about'), lastmod: null },
|
||||
{ loc: absoluteUrl('/blog'), lastmod: null },
|
||||
...docs.map((post) => ({
|
||||
loc: absoluteUrl(`/blog/${post.slug}`),
|
||||
lastmod: new Date(String(post.updatedAt ?? post.publishedAt)).toISOString(),
|
||||
})),
|
||||
];
|
||||
|
||||
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${urls.map(({ loc, lastmod }) =>
|
||||
` <url><loc>${loc}</loc>${lastmod ? `<lastmod>${lastmod}</lastmod>` : ''}</url>`,
|
||||
).join('\n')}
|
||||
</urlset>`;
|
||||
|
||||
return new Response(xml, {
|
||||
headers: { 'Content-Type': 'application/xml; charset=utf-8' },
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user