blog.hangerthem.com/components/content/MarkdownPage.tsx
HangerThem fd96e4b7fc feat(seo): sitemap, robots, canonical URLs, OG images and JSON-LD
- Add sitemap.ts and robots.ts
- pageMetadata() helper: consistent title, canonical URL, Open Graph,
  Twitter card and feed alternate on every page
- Generated OG images: a site card, and per post either the cover image
  or a branded title card (pre-rendered at build)
- BlogPosting JSON-LD on post pages
- Fail fast with a clear error when SITE_URL is missing

Content loading:
- Validate post frontmatter at build time and normalize dates to ISO
  (fixes invalid <time datetime> values)
- Add draft: true support (drafts only render in development)
- Cache markdown rendering so metadata and page share one render
- Replace the three copy-pasted about/projects/setup pages with a shared
  MarkdownPage component

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:22:40 +02:00

27 lines
1.1 KiB
TypeScript

import { pageMetadata } from '@/app/metadata'
import { TableOfContents } from '@/components/post/TableOfContents'
import { getPageBySlug } from '@/lib/posts'
import type { Metadata } from 'next'
/** Metadata for a standalone page served at `/<slug>` from content/pages/<slug>.md. */
export async function markdownPageMetadata(slug: string): Promise<Metadata> {
const { meta } = await getPageBySlug(slug)
return pageMetadata({ title: meta.title, description: meta.description, path: `/${slug}` })
}
/** Renders a standalone page from content/pages/<slug>.md. */
export async function MarkdownPage({ slug }: { slug: string }) {
const { meta, content, tableOfContents } = await getPageBySlug(slug)
return (
<main className="flex w-full">
<article className="max-w-3xl 2xl:max-w-4xl mx-auto px-4 py-8 mb-8 lg:py-16 min-w-0">
<h1 className="text-4xl font-bold mb-4">{meta.title}</h1>
<p className="text-fg text-lg mb-4">{meta.description}</p>
<div className="prose">{content}</div>
</article>
<TableOfContents items={tableOfContents} />
</main>
)
}