1- import { allBlogs } from "contentlayer/generated" ;
21import type { Metadata } from "next" ;
32import Image from "next/image" ;
43import { notFound } from "next/navigation" ;
54import { MDXComponent } from "@/components/MDXComponents" ;
65import { Header1 } from "@/components/ui/Headers" ;
76import Link from "@/components/ui/Link" ;
87import { meta } from "@/config/metadata" ;
8+ import { getBlogPosts } from "@/lib/blogUtils" ;
99import { cn } from "@/lib/utils" ;
1010import { parseISO } from "@/lib/utils" ;
1111import Avatar from "@/public/assets/avatar.png" ;
1212
1313export function generateStaticParams ( ) {
14- return allBlogs . map ( ( post ) => ( {
14+ const posts = getBlogPosts ( ) ;
15+
16+ return posts . map ( ( post ) => ( {
1517 slug : post . slug ,
1618 } ) ) ;
1719}
1820
21+ export const dynamicParams = false ;
22+
1923type Props = {
2024 params : Promise < { slug : string } > ;
2125} ;
2226
23- export async function generateMetadata ( { params } : Props ) : Promise < Metadata | undefined > {
24- const metadataParams = await params ;
25- const post = allBlogs . find ( ( post ) => post ? .slug === metadataParams ?. slug ) ;
27+ export async function generateMetadata ( { params } : Props ) : Promise < Metadata > {
28+ const { slug } = await params ;
29+ const post = getBlogPosts ( ) . find ( ( post ) => post . slug === slug ) ;
2630
2731 if ( ! post ) return { } ;
2832
29- const { title, publishedAt : publishedTime , summary : description , slug } = post ;
33+ const { title, publishedAt : publishedTime , summary : description } = post . metadata ;
3034
3135 return {
3236 title,
@@ -36,7 +40,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata | un
3640 description,
3741 type : "article" ,
3842 publishedTime,
39- url : `${ meta . url } /blog/${ slug } ` ,
43+ url : `${ meta . url } /blog/${ post . slug } ` ,
4044 } ,
4145 twitter : {
4246 card : "summary_large_image" ,
@@ -47,56 +51,71 @@ export async function generateMetadata({ params }: Props): Promise<Metadata | un
4751}
4852
4953export default async function Blog ( { params } : Props ) {
50- const blogParams = await params ;
51- const post = allBlogs . find ( ( post ) => post . slug === blogParams . slug ) ;
54+ const { slug } = await params ;
55+ const post = getBlogPosts ( ) . find ( ( post ) => post . slug === slug ) ;
5256
53- if ( ! post ) return notFound ( ) ;
57+ if ( ! post ) notFound ( ) ;
5458
5559 return (
5660 < article className = "mt-6 mb-16 flex min-h-screen flex-col items-start justify-center md:mt-12 lg:mt-20" >
5761 < script
5862 type = "application/ld+json"
59- // biome-ignore lint/security/noDangerouslySetInnerHtml: We trust the content of the JSON object
63+ suppressHydrationWarning
6064 dangerouslySetInnerHTML = { {
61- __html : JSON . stringify ( post . structuredData ) ,
65+ __html : JSON . stringify ( {
66+ "@context" : "https://schema.org" ,
67+ "@type" : "BlogPosting" ,
68+ headline : post . metadata . title ,
69+ datePublished : post . metadata . publishedAt ,
70+ dateModified : post . metadata . publishedAt ,
71+ description : post . metadata . summary ,
72+ image : post . metadata . image ? `${ meta . url } ${ post . metadata . image } ` : `/og?title=${ encodeURIComponent ( post . metadata . title ) } ` ,
73+ url : `${ meta . url } /blog/${ post . slug } ` ,
74+ author : {
75+ "@type" : "Person" ,
76+ name : post . metadata . author ,
77+ } ,
78+ } ) ,
6279 } }
6380 />
81+
6482 < div className = "grid flex-1 grid-cols-1 md:grid-cols-[1fr_minmax(auto,640px)_1fr] md:*:col-start-2" >
6583 < div >
6684 < header className = "mb-4 w-full" >
67- < Header1 > { post . title } </ Header1 >
85+ < Header1 > { post . metadata . title } </ Header1 >
6886 < div className = "mt-2 flex w-full flex-col items-start justify-between md:flex-row md:items-center" >
6987 < div className = "flex items-center" >
7088 < Image alt = { meta . title } height = { 24 } width = { 24 } src = { Avatar } className = "rounded-full" />
71- < time className = "ml-2 text-sm text-neutral-700 dark:text-neutral-300" dateTime = { new Date ( post . publishedAt ) . toUTCString ( ) } >
72- { post . author } / { parseISO ( post . publishedAt ) }
89+ < time className = "ml-2 text-sm text-neutral-700 dark:text-neutral-300" dateTime = { new Date ( post . metadata . publishedAt ) . toUTCString ( ) } >
90+ { post . metadata . author } / { parseISO ( post . metadata . publishedAt ) }
7391 </ time >
7492 </ div >
7593 < p className = "mt-2 min-w-32 text-sm text-neutral-700 md:mt-0 dark:text-neutral-300" >
7694 { post . wordCount } words • { post . readingTime ?. text }
7795 </ p >
7896 </ div >
7997 </ header >
80- < MDXComponent code = { post . body . code } />
98+ < MDXComponent source = { post . content } />
8199 </ div >
82100 < div className = "sticky top-24 col-start-3! mt-8 ml-12 hidden max-w-56 flex-col space-y-2 self-start text-base xl:flex" >
83101 < p className = "mb-2 text-sm uppercase" > On this page</ p >
84- { post . headings . map ( ( props : { size : number ; content : string ; slug : string } ) => (
85- < Link
86- key = { props . slug }
87- href = { `#${ props . slug } ` }
88- scroll = { true }
89- className = { cn (
90- {
91- "ml-2" : props . size === 2 ,
92- "ml-4" : props . size === 3 ,
93- } ,
94- "font-normal! no-underline opacity-50 duration-200 hover:underline hover:opacity-100 motion-reduce:transition-none"
95- ) }
96- >
97- { props . content }
98- </ Link >
99- ) ) }
102+ { post . headings &&
103+ post . headings . map ( ( props : { size : number ; content : string ; slug : string } ) => (
104+ < Link
105+ key = { props . slug }
106+ href = { `#${ props . slug } ` }
107+ scroll = { true }
108+ className = { cn (
109+ {
110+ "ml-2" : props . size === 2 ,
111+ "ml-4" : props . size === 3 ,
112+ } ,
113+ "font-normal! no-underline opacity-50 duration-200 hover:underline hover:opacity-100 motion-reduce:transition-none"
114+ ) }
115+ >
116+ { props . content }
117+ </ Link >
118+ ) ) }
100119 </ div >
101120 </ div >
102121 < div className = "flex w-full justify-end py-4 text-neutral-700 dark:text-neutral-300" >
0 commit comments