feat: fix blog page

This commit is contained in:
Kalidou Diagne
2025-04-21 14:43:38 +03:00
parent b92cf4f75c
commit df620631d7
3 changed files with 168 additions and 92 deletions

View File

@@ -7,100 +7,139 @@ import { Markdown } from "@/components/ui/markdown"
import { getArticles, getArticleById } from "@/lib/blog"
import { Metadata } from "next"
import Link from "next/link"
import { notFound } from "next/navigation"
export const generateStaticParams = async () => {
const articles = await getArticles()
return articles.map(({ id }) => ({
slug: id,
}))
try {
const articles = await getArticles()
return articles.map(({ id }) => ({
slug: id,
}))
} catch (error) {
console.error("Error generating static params for blog articles:", error)
return []
}
}
export async function generateMetadata({ params }: any): Promise<Metadata> {
const post = await getArticleById(params.slug)
try {
const post = await getArticleById(params.slug)
const imageUrl =
(post?.image ?? "")?.length > 0
? `/articles/${post?.id}/${post?.image}`
: "/og-image.png"
if (!post) {
return {
title: "Article Not Found",
description: "The requested article could not be found.",
}
}
const metadata: Metadata = {
title: post?.title,
description: post?.tldr,
openGraph: {
images: [{ url: imageUrl, width: 1200, height: 630 }],
},
}
const imageUrl =
(post?.image ?? "")?.length > 0
? `/articles/${post?.id}/${post?.image}`
: "/og-image.png"
// Add canonical URL if post has canonical property
if (post && "canonical" in post) {
metadata.alternates = {
canonical: post.canonical as string,
const metadata: Metadata = {
title: post?.title,
description: post?.tldr,
openGraph: {
images: [{ url: imageUrl, width: 1200, height: 630 }],
},
}
// Add canonical URL if post has canonical property
if (post && "canonical" in post) {
metadata.alternates = {
canonical: post.canonical as string,
}
}
return metadata
} catch (error) {
console.error(`Error generating metadata for slug ${params.slug}:`, error)
return {
title: "Blog Article",
description: "An error occurred while loading this article.",
}
}
return metadata
}
export default function BlogArticle({ params }: any) {
const slug = params.slug
const post = getArticleById(slug)
export default async function BlogArticle({ params }: any) {
try {
const slug = params.slug
const post = await getArticleById(slug)
if (!post) return null
return (
<div className="flex flex-col">
<div className="flex items-start justify-center background-gradient z-0">
<div className="w-full bg-cover-gradient border-b border-tuatara-300">
<AppContent className="flex flex-col gap-8 py-10 max-w-[978px]">
<Label.PageTitle label={post?.title} />
{post?.date || post?.tldr ? (
<div className="flex flex-col gap-2">
{post?.date && (
<div
className={blogArticleCardTagCardVariants({
variant: "secondary",
})}
>
{new Date(post?.date).toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
})}
</div>
)}
{post?.canonical && (
<div className="text-sm italic text-gray-500 mt-1">
This post was originally posted in{" "}
<a
href={post.canonical}
target="_blank"
rel="noopener noreferrer canonical"
className="text-primary hover:underline"
if (!post) {
notFound()
}
return (
<div className="flex flex-col">
<div className="flex items-start justify-center background-gradient z-0">
<div className="w-full bg-cover-gradient border-b border-tuatara-300">
<AppContent className="flex flex-col gap-8 py-10 max-w-[978px]">
<Label.PageTitle label={post?.title} />
{post?.date || post?.tldr ? (
<div className="flex flex-col gap-2">
{post?.date && (
<div
className={blogArticleCardTagCardVariants({
variant: "secondary",
})}
>
{new URL(post.canonical).hostname.replace(/^www\./, "")}
</a>
</div>
)}
{post?.tldr && <Markdown>{post?.tldr}</Markdown>}
</div>
) : null}
{(post?.tags ?? [])?.length > 0 && (
<div className="flex flex-col gap-2">
<span className="text-sm italic text-tuatara-950">Tags:</span>
<div className="flex gap-2">
{post?.tags?.map((tag) => (
<Link key={tag} href={`/${params.lang}/blog?tag=${tag}`}>
<Button size="xs">{tag}</Button>
</Link>
))}
{new Date(post?.date).toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
})}
</div>
)}
{post?.canonical && (
<div className="text-sm italic text-gray-500 mt-1">
This post was originally posted in{" "}
<a
href={post.canonical}
target="_blank"
rel="noopener noreferrer canonical"
className="text-primary hover:underline"
>
{new URL(post.canonical).hostname.replace(/^www\./, "")}
</a>
</div>
)}
{post?.tldr && <Markdown>{post?.tldr}</Markdown>}
</div>
</div>
)}
</AppContent>
) : null}
{(post?.tags ?? [])?.length > 0 && (
<div className="flex flex-col gap-2">
<span className="text-sm italic text-tuatara-950">Tags:</span>
<div className="flex gap-2">
{post?.tags?.map((tag) => (
<Link key={tag} href={`/${params.lang}/blog?tag=${tag}`}>
<Button size="xs">{tag}</Button>
</Link>
))}
</div>
</div>
)}
</AppContent>
</div>
</div>
<div className="pt-10 md:pt-16 pb-32">
<BlogContent post={post} />
</div>
</div>
<div className="pt-10 md:pt-16 pb-32">
<BlogContent post={post} />
)
} catch (error) {
console.error(`Error rendering blog article ${params.slug}:`, error)
return (
<div className="flex flex-col items-center justify-center py-20">
<h1 className="text-2xl font-bold mb-4">Error Loading Article</h1>
<p className="text-gray-600 mb-8">
There was a problem loading this article. Please try again later.
</p>
<Link href={`/${params.lang}/blog`}>
<Button>Return to Blog</Button>
</Link>
</div>
</div>
)
)
}
}

View File

@@ -2,7 +2,7 @@ import { useTranslation } from "@/app/i18n"
import { BlogArticles } from "@/components/blog/blog-articles"
import { AppContent } from "@/components/ui/app-content"
import { Label } from "@/components/ui/label"
import { getArticles } from "@/lib/blog"
import { Article, getArticles } from "@/lib/blog"
import { Metadata } from "next"
export const metadata: Metadata = {
@@ -18,11 +18,14 @@ interface BlogPageProps {
const BlogPage = async ({ params: { lang }, searchParams }: BlogPageProps) => {
const { t } = await useTranslation(lang, "blog-page")
// Get the tag from searchParams
const tag = searchParams?.tag as string | undefined
// Fetch articles, filtering by tag if present
const articles = await getArticles({ tag })
let articles: Article[] = []
try {
articles = await getArticles({ tag })
} catch (error) {
console.error("Error fetching blog articles:", error)
}
return (
<div className="flex flex-col">

View File

@@ -23,20 +23,43 @@ const articlesDirectory = path.join(process.cwd(), "articles")
// Get all articles from /articles
export function getArticles(options?: { limit?: number; tag?: string }) {
const { limit = 1000, tag } = options ?? {}
// Get file names under /articles
const fileNames = fs.readdirSync(articlesDirectory)
if (!fs.existsSync(articlesDirectory)) {
console.error(`Articles directory not found at ${articlesDirectory}`)
return []
}
let fileNames = []
try {
fileNames = fs.readdirSync(articlesDirectory)
} catch (error) {
console.error(`Error reading articles directory: ${error}`)
return []
}
const allArticlesData = fileNames.map((fileName: string) => {
const id = fileName.replace(/\.md$/, "")
if (id.toLowerCase() === "readme") {
return null
}
// Read markdown file as string
const fullPath = path.join(articlesDirectory, fileName)
const fileContents = fs.readFileSync(fullPath, "utf8")
let fileContents
try {
fileContents = fs.readFileSync(fullPath, "utf8")
} catch (error) {
console.error(`Error reading file ${fileName}: ${error}`)
return {
id,
title: `Error reading ${id}`,
content: "This article could not be read due to an error.",
date: new Date().toISOString().split("T")[0],
tags: [],
}
}
try {
// Use matter with options to handle multiline strings
const matterResult = matter(fileContents, {
engines: {
yaml: {
@@ -65,9 +88,11 @@ export function getArticles(options?: { limit?: number; tag?: string }) {
return {
id,
title: matterResult.data.title || `Article ${id}`,
content: matterResult.content || "",
date: matterResult.data.date || new Date().toISOString().split("T")[0],
...matterResult.data,
tags: tags, // Assign the combined and normalized tags array
content: matterResult.content,
}
} catch (error) {
console.error(`Error processing ${fileName}:`, error)
@@ -97,6 +122,9 @@ export function getArticles(options?: { limit?: number; tag?: string }) {
const dateA = new Date(a.date)
const dateB = new Date(b.date)
if (isNaN(dateA.getTime())) return 1
if (isNaN(dateB.getTime())) return -1
// Sort in descending order (newest first)
return dateB.getTime() - dateA.getTime()
})
@@ -105,11 +133,17 @@ export function getArticles(options?: { limit?: number; tag?: string }) {
}
export function getArticleById(slug?: string) {
// Note: This might need adjustment if you expect getArticleById to also have tags
// Currently relies on the base getArticles() which fetches all tags
const articles = getArticles() // Fetch all articles to find the one by ID
if (!slug) return null
return articles.find((article) => article.id === slug)
try {
// Note: This might need adjustment if you expect getArticleById to also have tags
// Currently relies on the base getArticles() which fetches all tags
const articles = getArticles() // Fetch all articles to find the one by ID
return articles.find((article) => article.id === slug)
} catch (error) {
console.error(`Error in getArticleById for slug ${slug}:`, error)
return null
}
}
const lib = { getArticles, getArticleById }