blog.hangerthem.com/app/posts/[slug]/page.tsx
2026-08-03 12:33:39 +02:00

147 lines
4.8 KiB
TypeScript

import { metadata } from '@/app/metadata'
import { ImageWrapper } from '@/components/common/ImageWrapper'
import { ShareLinks } from '@/components/common/ShareLinks'
import { TableOfContents } from '@/components/common/TableOfContents'
import { getAllPostSlugs, getNextAndPreviousPosts, getPostBySlug } from '@/lib/posts'
import { slugify } from '@/utils/slug'
import type { Metadata } from 'next'
import Link from 'next/link'
export async function generateStaticParams() {
return getAllPostSlugs().map((slug) => ({ slug }))
}
export const dynamicParams = false
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>
}): Promise<Metadata> {
const { slug } = await params
const { meta } = await getPostBySlug(slug)
return {
title: `${meta.title} | ${metadata.title!.toString()}`,
description: meta.description,
openGraph: {
type: 'article',
title: `${meta.title} | ${metadata.title!.toString()}`,
description: meta.description,
url: process.env.SITE_URL + `/posts/${slug}`,
images: meta.coverImage ? [meta.coverImage] : undefined,
tags: meta.tags,
...(meta.updated && { modifiedTime: new Date(meta.updated).toISOString() }),
siteName: metadata.title!.toString(),
},
}
}
export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params
const { meta, content, tableOfContents } = await getPostBySlug(slug)
const { next, previous } = getNextAndPreviousPosts(slug)
return (
<main className="flex w-full">
<article className="max-w-3xl mx-auto px-4 py-8 lg:py-16">
{meta.category && (
<Link
href={`/categories/${slugify(meta.category)}`}
className="text-accent hover:underline font-sm mb-2 block"
>
{meta.category}
</Link>
)}
<h1 className="text-4xl font-bold mb-4">{meta.title}</h1>
<div className="flex items-center gap-2 text-fg-dim mb-4 border-y border-border py-2 px-4 text-sm">
<div className="flex items-center gap-2">
<span>Posted</span>
<time
dateTime={meta.date}
title={new Date(meta.date).toLocaleString()}
className="font-semibold"
>
{new Date(meta.date).toLocaleDateString(undefined, {
day: 'numeric',
month: 'long',
year: 'numeric',
})}
</time>
</div>
<span>&middot;</span>
{meta.updated && (
<>
<div className="flex items-center gap-2">
<span>Updated</span>
<time
dateTime={meta.updated}
title={new Date(meta.updated).toLocaleString()}
className="font-semibold"
>
{new Date(meta.updated).toLocaleDateString(undefined, {
day: 'numeric',
month: 'long',
year: 'numeric',
})}
</time>
</div>
<span>&middot;</span>
</>
)}
<span>{meta.readingTimeText}</span>
</div>
<p className="text-fg text-lg mb-4">{meta.description}</p>
{meta.coverImage && <ImageWrapper src={meta.coverImage} alt={meta.coverImageAlt} />}
<div className="prose">{content}</div>
{meta.tags && (
<div className="mt-6 border-t border-border pt-4 flex gap-2 items-center flex-wrap">
<h2 className="text-lg font-bold">Tags</h2>
<div className="flex flex-wrap gap-1">
{meta.tags.map((tag) => (
<span key={tag} className="bg-accent/20 text-accent px-3 py-1 rounded-full text-xs">
{tag}
</span>
))}
</div>
</div>
)}
<div className="mt-8 mx-auto flex">
<ShareLinks
url={process.env.SITE_URL + `/posts/${slug}`}
title={meta.title}
description={meta.description}
/>
</div>
<div className="mt-8 flex justify-between">
{previous ? (
<Link
href={`/posts/${previous.slug}`}
className="text-accent hover:underline font-sm mb-2 block"
>
&larr; {previous.title}
</Link>
) : (
<div />
)}
{next ? (
<Link
href={`/posts/${next.slug}`}
className="text-accent hover:underline font-sm mb-2 block"
>
{next.title} &rarr;
</Link>
) : (
<div />
)}
</div>
</article>
<aside className="hidden lg:block w-80">
<TableOfContents items={tableOfContents} />
</aside>
</main>
)
}