1+ import { getBlogMetadata , getAllBlogsMetadata } from '@/lib/blogs' ;
2+ import { Metadata } from 'next' ;
3+ import fs from 'fs' ;
4+ import path from 'path' ;
5+ import matter from 'gray-matter' ;
6+ import ReactMarkdown from 'react-markdown' ;
7+ import remarkGfm from 'remark-gfm' ;
8+
9+ export default async function Page ( {
10+ params,
11+ } : {
12+ params : Promise < { slug : string } > ;
13+ } ) {
14+ const { slug } = await params ;
15+ const metadata = getBlogMetadata ( slug ) ;
16+
17+ // Read and parse the MDX file
18+ const blogsDirectory = path . join ( process . cwd ( ) , 'content/blog' ) ;
19+ const filePath = path . join ( blogsDirectory , `${ metadata . filename } .mdx` ) ;
20+ const fileContents = fs . readFileSync ( filePath , 'utf8' ) ;
21+ const { content } = matter ( fileContents ) ;
22+
23+ return (
24+ < article className = "max-w-4xl mx-auto py-8" >
25+ < header className = "mb-8 border-b pb-6" >
26+ < h1 className = "text-xl font-bold mb-4 text-foreground" >
27+ { metadata . title }
28+ </ h1 >
29+ < div className = "flex flex-wrap items-center gap-4 text-sm text-muted-foreground" >
30+ < time dateTime = { metadata . date } >
31+ { new Date ( metadata . date ) . toLocaleDateString ( "en-US" , {
32+ year : "numeric" ,
33+ month : "long" ,
34+ day : "numeric" ,
35+ } ) }
36+ </ time >
37+ < span className = "px-2 py-1 bg-muted rounded-full text-xs" >
38+ { metadata . category }
39+ </ span >
40+ </ div >
41+ </ header >
42+
43+ < div className = "text-muted-foreground leading-relaxed" >
44+ < ReactMarkdown
45+ remarkPlugins = { [ remarkGfm ] }
46+ components = { {
47+ h1 : ( { children } ) => (
48+ < h1 className = "text-xl font-semibold text-foreground mb-4 mt-8" >
49+ { children }
50+ </ h1 >
51+ ) ,
52+ h2 : ( { children } ) => (
53+ < h2 className = "text-lg font-semibold text-foreground mb-3 mt-6" >
54+ { children }
55+ </ h2 >
56+ ) ,
57+ h3 : ( { children } ) => (
58+ < h3 className = "text-base font-semibold text-foreground mb-2 mt-4" >
59+ { children }
60+ </ h3 >
61+ ) ,
62+ p : ( { children } ) => (
63+ < p className = "text-muted-foreground leading-relaxed mb-4" >
64+ { children }
65+ </ p >
66+ ) ,
67+ ul : ( { children } ) => (
68+ < ul className = "list-disc list-inside mb-4 text-muted-foreground" >
69+ { children }
70+ </ ul >
71+ ) ,
72+ ol : ( { children } ) => (
73+ < ol className = "list-decimal list-inside mb-4 text-muted-foreground" >
74+ { children }
75+ </ ol >
76+ ) ,
77+ li : ( { children } ) => < li className = "mb-1" > { children } </ li > ,
78+ code : ( { children } ) => (
79+ < code className = "bg-muted px-1 py-0.5 rounded text-sm font-mono" >
80+ { children }
81+ </ code >
82+ ) ,
83+ pre : ( { children } ) => (
84+ < pre className = "bg-muted p-4 rounded-lg overflow-x-auto mb-4" >
85+ { children }
86+ </ pre >
87+ ) ,
88+ blockquote : ( { children } ) => (
89+ < blockquote className = "border-l-4 border-muted pl-4 italic text-muted-foreground/80 mb-4" >
90+ { children }
91+ </ blockquote >
92+ ) ,
93+ a : ( { href, children } ) => (
94+ < a
95+ href = { href }
96+ className = "text-primary hover:text-primary/80 underline transition-colors"
97+ >
98+ { children }
99+ </ a >
100+ ) ,
101+ } }
102+ >
103+ { content }
104+ </ ReactMarkdown >
105+ </ div >
106+ </ article >
107+ ) ;
108+ }
109+
110+ export async function generateMetadata ( {
111+ params,
112+ } : {
113+ params : Promise < { slug : string } > ;
114+ } ) : Promise < Metadata > {
115+ const { slug } = await params ;
116+ const metadata = getBlogMetadata ( slug ) ;
117+
118+ return {
119+ title : metadata . title ,
120+ description : `${ metadata . category } blog from ${ metadata . date } ` ,
121+ keywords : metadata . tags ,
122+ } ;
123+ }
124+
125+ export function generateStaticParams ( ) {
126+ const blogs = getAllBlogsMetadata ( ) ;
127+ return blogs . map ( blog => ( { slug : blog . slug } ) ) ;
128+ }
129+
130+ export const dynamicParams = false ;
0 commit comments