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
@@ -32,3 +32,47 @@ export function organizationJsonLd() {
|
||||
logo: absoluteUrl('/icon-512.png'),
|
||||
} as const;
|
||||
}
|
||||
|
||||
interface PostSummary {
|
||||
title: string;
|
||||
slug: string;
|
||||
excerpt: string;
|
||||
publishedAt: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* References the Person and Organization by @id rather than repeating them, so
|
||||
* search engines resolve every post and the About page to the one author
|
||||
* entity. Repeating the shape would declare several people with one name.
|
||||
*/
|
||||
export function blogPostingJsonLd(post: PostSummary) {
|
||||
return {
|
||||
'@type': 'BlogPosting',
|
||||
headline: post.title,
|
||||
description: post.excerpt,
|
||||
url: absoluteUrl(`/blog/${post.slug}`),
|
||||
datePublished: post.publishedAt,
|
||||
author: { '@id': `${SITE_URL}/about#tudor` },
|
||||
publisher: { '@id': `${SITE_URL}#organization` },
|
||||
} as const;
|
||||
}
|
||||
|
||||
export function breadcrumbJsonLd(post: PostSummary) {
|
||||
return {
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: [
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 1,
|
||||
name: 'Blog',
|
||||
item: absoluteUrl('/blog'),
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 2,
|
||||
name: post.title,
|
||||
item: absoluteUrl(`/blog/${post.slug}`),
|
||||
},
|
||||
],
|
||||
} as const;
|
||||
}
|
||||
Reference in new issue
Block a user