101 lines
2.5 KiB
TypeScript
101 lines
2.5 KiB
TypeScript
import { notFound } from 'next/navigation'
|
|
import { CustomMDX } from '@/components/mdx'
|
|
import { formatDate, getBlogPosts } from '../utils'
|
|
import { baseUrl } from '../../sitemap'
|
|
|
|
export async function generateStaticParams() {
|
|
let posts = getBlogPosts()
|
|
|
|
return posts.map((post) => ({
|
|
slug: post.slug,
|
|
}))
|
|
}
|
|
|
|
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
|
|
const { slug } = await params;
|
|
const post = getBlogPosts().find((post) => post.slug === slug)
|
|
if (!post) {
|
|
return
|
|
}
|
|
|
|
const {
|
|
title,
|
|
publishedAt: publishedTime,
|
|
summary: description,
|
|
image,
|
|
} = post.metadata
|
|
const ogImage = image
|
|
? image
|
|
: `${baseUrl}/og?title=${encodeURIComponent(title)}`
|
|
|
|
return {
|
|
title,
|
|
description,
|
|
openGraph: {
|
|
title,
|
|
description,
|
|
type: 'article',
|
|
publishedTime,
|
|
url: `${baseUrl}/blog/${post.slug}`,
|
|
images: [
|
|
{
|
|
url: ogImage,
|
|
},
|
|
],
|
|
},
|
|
twitter: {
|
|
card: 'summary_large_image',
|
|
title,
|
|
description,
|
|
images: [ogImage],
|
|
},
|
|
}
|
|
}
|
|
|
|
export default async function Blog({ params }: { params: Promise<{ slug: string }> }) {
|
|
const { slug } = await params;
|
|
const post = getBlogPosts().find((post) => post.slug === slug)
|
|
|
|
if (!post) {
|
|
notFound()
|
|
}
|
|
|
|
return (
|
|
<section>
|
|
<script
|
|
type="application/ld+json"
|
|
suppressHydrationWarning
|
|
dangerouslySetInnerHTML={{
|
|
__html: JSON.stringify({
|
|
'@context': 'https://schema.org',
|
|
'@type': 'BlogPosting',
|
|
headline: post.metadata.title,
|
|
datePublished: post.metadata.publishedAt,
|
|
dateModified: post.metadata.publishedAt,
|
|
description: post.metadata.summary,
|
|
image: post.metadata.image
|
|
? `${baseUrl}${post.metadata.image}`
|
|
: `/og?title=${encodeURIComponent(post.metadata.title)}`,
|
|
url: `${baseUrl}/blog/${post.slug}`,
|
|
author: {
|
|
'@type': 'Person',
|
|
name: 'My Portfolio',
|
|
},
|
|
}),
|
|
}}
|
|
/>
|
|
<h1 className="title font-semibold text-2xl tracking-tighter">
|
|
{post.metadata.title}
|
|
</h1>
|
|
<div className="flex justify-between items-center mt-2 mb-8 text-sm">
|
|
<p className="text-sm text-neutral-600 dark:text-neutral-400">
|
|
{formatDate(post.metadata.publishedAt)}
|
|
</p>
|
|
</div>
|
|
<article className="prose">
|
|
<CustomMDX source={post.content} />
|
|
</article>
|
|
</section>
|
|
)
|
|
}
|