Skip to content

Commit d8d8f29

Browse files
committed
Add blogs in website
Signed-off-by: Amrish Kushwaha <askmaurya48@gmail.com>
1 parent f348651 commit d8d8f29

10 files changed

Lines changed: 703 additions & 83 deletions

File tree

app/blog/[slug]/page.tsx

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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;

app/blog/blog-client.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import Head from "../../src/components/Head";
5+
import { Search } from "../../src/components/Search";
6+
import { AllPosts } from "../../src/components/AllPosts";
7+
import { usePosts } from "../../src/graphql/allPostsQuery";
8+
import { searchByPostsOrTags } from "../../src/utils/search";
9+
import { BlogMetadata } from "@/lib/blogs";
10+
11+
function convertBlogToPost(blog: BlogMetadata) {
12+
return {
13+
title: blog.title,
14+
date: new Date(blog.date),
15+
tags: blog.tags || [],
16+
featured_image: null,
17+
excerpt: `Local blog post in category: ${blog.category}`,
18+
slug: `/blog/${blog.slug}`,
19+
category: blog.category,
20+
filename: blog.filename
21+
};
22+
}
23+
24+
interface BlogClientPageProps {
25+
mdxBlogs: BlogMetadata[];
26+
}
27+
28+
const BlogClientPage = ({ mdxBlogs }: BlogClientPageProps) => {
29+
const [search, setSearch] = React.useState("");
30+
31+
const jsonPosts = usePosts();
32+
33+
const mdxPosts = mdxBlogs.map(convertBlogToPost);
34+
35+
const allPosts = [...mdxPosts, ...jsonPosts].sort((a, b) =>
36+
new Date(b.date).getTime() - new Date(a.date).getTime()
37+
);
38+
39+
const searchedPosts = searchByPostsOrTags(allPosts, search);
40+
41+
return (
42+
<>
43+
<Head title="Blog List" />
44+
<div className="max-w-4xl mx-auto py-8">
45+
<h2 className="text-2xl pt-4">Blog</h2>
46+
<p>
47+
<strong className="bold">Notes:</strong> Currently listed the posts
48+
which I have written on other platforms such as medium and my website,
49+
as well as local blog posts.
50+
</p>
51+
<Search search={search} setSearch={setSearch} />
52+
<AllPosts posts={searchedPosts} search={search} />
53+
</div>
54+
</>
55+
);
56+
};
57+
58+
export default BlogClientPage;

app/blog/blogs-client.tsx

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import Link from "next/link";
5+
import { Search } from "../../src/components/Search";
6+
import { BlogMetadata } from "@/lib/blogs";
7+
8+
interface BlogsClientComponentProps {
9+
blogs: BlogMetadata[];
10+
}
11+
12+
export const BlogsClientComponent = ({ blogs }: BlogsClientComponentProps) => {
13+
const [search, setSearch] = React.useState("");
14+
15+
// Filter blogs based on search
16+
const filteredBlogs = blogs.filter(blog =>
17+
(blog.title?.toLowerCase() || '').includes(search.toLowerCase()) ||
18+
(blog.category?.toLowerCase() || '').includes(search.toLowerCase()) ||
19+
(blog.tags || []).some(tag => tag?.toLowerCase().includes(search.toLowerCase()))
20+
);
21+
22+
return (
23+
<div className="max-w-4xl mx-auto py-8">
24+
<h2 className="text-3xl font-bold mb-4">Blog</h2>
25+
<p className="text-muted-foreground mb-6">
26+
<strong>Notes:</strong> Currently listed the posts which I have written on other platforms such as medium and my website.
27+
</p>
28+
29+
<Search search={search} setSearch={setSearch} />
30+
31+
<div className="space-y-6 mt-8">
32+
{filteredBlogs.map((blog) => (
33+
<article key={blog.slug} className="border rounded-lg p-6 hover:shadow-md transition-shadow">
34+
<Link href={`/blog/${blog.slug}`} className="block">
35+
<h3 className="text-xl font-semibold mb-2 hover:text-primary transition-colors">
36+
{blog.title}
37+
</h3>
38+
<div className="flex flex-wrap items-center gap-3 text-sm text-muted-foreground mb-3">
39+
<time dateTime={blog.date}>
40+
{new Date(blog.date).toLocaleDateString('en-US', {
41+
year: 'numeric',
42+
month: 'long',
43+
day: 'numeric'
44+
})}
45+
</time>
46+
<span className="px-2 py-1 bg-muted rounded-full text-xs">
47+
{blog.category}
48+
</span>
49+
</div>
50+
<div className="flex flex-wrap gap-1">
51+
{blog.tags.map(tag => (
52+
<span key={tag} className="px-2 py-1 bg-muted/50 rounded-full text-xs">
53+
#{tag}
54+
</span>
55+
))}
56+
</div>
57+
</Link>
58+
</article>
59+
))}
60+
61+
{filteredBlogs.length === 0 && (
62+
<p className="text-center text-muted-foreground py-8">
63+
No blogs found matching your search.
64+
</p>
65+
)}
66+
</div>
67+
</div>
68+
);
69+
};

app/blog/page.tsx

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,10 @@
1-
"use client";
1+
import { getAllBlogsMetadata } from "@/lib/blogs";
2+
import BlogClientPage from "./blog-client.tsx";
23

3-
import * as React from "react";
4-
import Head from "../../src/components/Head";
5-
import { Search } from "../../src/components/Search";
6-
import { AllPosts } from "../../src/components/AllPosts";
7-
import { usePosts } from "../../src/graphql/allPostsQuery";
8-
import { searchByPostsOrTags } from "../../src/utils/search";
4+
const BlogPage = async () => {
5+
const mdxBlogs = getAllBlogsMetadata();
96

10-
const BlogPage = () => {
11-
const [search, setSearch] = React.useState("");
12-
const posts = usePosts();
13-
let searchedPosts = searchByPostsOrTags(posts, search);
14-
React.useEffect(() => {
15-
searchedPosts = searchByPostsOrTags(posts, search);
16-
}, [search]);
17-
18-
return (
19-
<>
20-
<Head title="Blog List" />
21-
<h2 className="text-2xl pt-4">Blog</h2>
22-
<p>
23-
<strong className="bold">Notes:</strong> Currently listed the posts
24-
which I have written on other platforms such as medium and my website.
25-
</p>
26-
<Search search={search} setSearch={setSearch} />
27-
<AllPosts posts={searchedPosts} search={search} />
28-
</>
29-
);
7+
return <BlogClientPage mdxBlogs={mdxBlogs} />;
308
};
319

3210
export default BlogPage;

0 commit comments

Comments
 (0)