Skip to content

Commit dee0344

Browse files
Copilothotlong
andcommitted
Add blog section with fumadocs integration
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 8e71906 commit dee0344

File tree

3 files changed

+197
-10
lines changed

3 files changed

+197
-10
lines changed
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import { notFound } from 'next/navigation';
2+
import { blog } from '@/app/source';
3+
import defaultMdxComponents from 'fumadocs-ui/mdx';
4+
import { HomeLayout } from 'fumadocs-ui/layouts/home';
5+
import { baseOptions } from '@/app/layout.config';
6+
import Link from 'next/link';
7+
import { ArrowLeft } from 'lucide-react';
8+
9+
export default async function BlogPage({
10+
params,
11+
}: {
12+
params: Promise<{ lang: string; slug?: string[] }>;
13+
}) {
14+
const { slug } = await params;
15+
16+
// If no slug, show blog index
17+
if (!slug || slug.length === 0) {
18+
const posts = blog.getPages();
19+
20+
return (
21+
<HomeLayout {...baseOptions}>
22+
<main className="container max-w-5xl mx-auto px-4 py-16">
23+
<div className="mb-12">
24+
<h1 className="text-4xl font-bold mb-4">Blog</h1>
25+
<p className="text-lg text-fd-muted-foreground">
26+
Insights, updates, and best practices from the ObjectStack team.
27+
</p>
28+
</div>
29+
30+
<div className="grid gap-8 md:grid-cols-2">
31+
{posts.map((post) => (
32+
<Link
33+
key={post.url}
34+
href={post.url}
35+
className="group block rounded-lg border border-fd-border bg-fd-card p-6 transition-all hover:border-fd-primary/30 hover:shadow-md"
36+
>
37+
<div className="mb-3">
38+
<h2 className="text-2xl font-semibold mb-2 group-hover:text-fd-primary transition-colors">
39+
{post.data.title}
40+
</h2>
41+
{post.data.description && (
42+
<p className="text-fd-muted-foreground">
43+
{post.data.description}
44+
</p>
45+
)}
46+
</div>
47+
48+
<div className="flex items-center gap-4 text-sm text-fd-muted-foreground">
49+
{post.data.date && (
50+
<time dateTime={post.data.date}>
51+
{new Date(post.data.date).toLocaleDateString('en-US', {
52+
year: 'numeric',
53+
month: 'long',
54+
day: 'numeric',
55+
})}
56+
</time>
57+
)}
58+
{post.data.author && (
59+
<span>By {post.data.author}</span>
60+
)}
61+
</div>
62+
63+
{post.data.tags && post.data.tags.length > 0 && (
64+
<div className="mt-4 flex flex-wrap gap-2">
65+
{post.data.tags.map((tag: string) => (
66+
<span
67+
key={tag}
68+
className="inline-flex items-center rounded-full bg-fd-primary/10 px-2.5 py-0.5 text-xs font-medium text-fd-primary"
69+
>
70+
{tag}
71+
</span>
72+
))}
73+
</div>
74+
)}
75+
</Link>
76+
))}
77+
</div>
78+
79+
{posts.length === 0 && (
80+
<div className="text-center py-12">
81+
<p className="text-fd-muted-foreground">No blog posts yet. Check back soon!</p>
82+
</div>
83+
)}
84+
</main>
85+
</HomeLayout>
86+
);
87+
}
88+
89+
// Show individual blog post
90+
const page = blog.getPage(slug);
91+
92+
if (!page) {
93+
notFound();
94+
}
95+
96+
const MDX = page.data.body;
97+
98+
return (
99+
<HomeLayout {...baseOptions}>
100+
<main className="container max-w-4xl mx-auto px-4 py-16">
101+
<Link
102+
href="/blog"
103+
className="inline-flex items-center gap-2 text-sm text-fd-muted-foreground hover:text-fd-foreground mb-8 transition-colors"
104+
>
105+
<ArrowLeft className="w-4 h-4" />
106+
Back to Blog
107+
</Link>
108+
109+
<article className="prose prose-neutral dark:prose-invert max-w-none">
110+
<header className="mb-8 pb-8 border-b border-fd-border">
111+
<h1 className="text-4xl font-bold mb-4">{page.data.title}</h1>
112+
113+
{page.data.description && (
114+
<p className="text-xl text-fd-muted-foreground mb-6">
115+
{page.data.description}
116+
</p>
117+
)}
118+
119+
<div className="flex items-center gap-4 text-sm text-fd-muted-foreground">
120+
{page.data.date && (
121+
<time dateTime={page.data.date}>
122+
{new Date(page.data.date).toLocaleDateString('en-US', {
123+
year: 'numeric',
124+
month: 'long',
125+
day: 'numeric',
126+
})}
127+
</time>
128+
)}
129+
{page.data.author && (
130+
<span>By {page.data.author}</span>
131+
)}
132+
</div>
133+
134+
{page.data.tags && page.data.tags.length > 0 && (
135+
<div className="mt-4 flex flex-wrap gap-2">
136+
{page.data.tags.map((tag: string) => (
137+
<span
138+
key={tag}
139+
className="inline-flex items-center rounded-full bg-fd-primary/10 px-3 py-1 text-sm font-medium text-fd-primary"
140+
>
141+
{tag}
142+
</span>
143+
))}
144+
</div>
145+
)}
146+
</header>
147+
148+
<MDX components={defaultMdxComponents} />
149+
</article>
150+
</main>
151+
</HomeLayout>
152+
);
153+
}
154+
155+
export async function generateStaticParams() {
156+
return blog.getPages().map((page) => ({
157+
slug: page.slugs,
158+
}));
159+
}
160+
161+
export async function generateMetadata({
162+
params,
163+
}: {
164+
params: Promise<{ slug?: string[] }>;
165+
}) {
166+
const { slug } = await params;
167+
168+
// If no slug, return default metadata for blog index
169+
if (!slug || slug.length === 0) {
170+
return {
171+
title: 'Blog',
172+
description: 'Insights, updates, and best practices from the ObjectStack team.',
173+
};
174+
}
175+
176+
const page = blog.getPage(slug);
177+
178+
if (!page) {
179+
notFound();
180+
}
181+
182+
return {
183+
title: page.data.title,
184+
description: page.data.description,
185+
};
186+
}

apps/docs/app/layout.config.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ export const baseOptions: BaseLayoutProps = {
2222
url: '/docs/',
2323
active: 'nested-url',
2424
},
25+
{
26+
text: 'Blog',
27+
url: '/blog',
28+
active: 'nested-url',
29+
},
2530
// {
2631
// text: 'Concepts',
2732
// url: '/docs/concepts/manifesto',

apps/docs/source.config.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineDocs, defineConfig } from 'fumadocs-mdx/config';
1+
import { defineDocs, defineConfig, frontmatterSchema } from 'fumadocs-mdx/config';
22
import { z } from 'zod';
33

44
export const docs = defineDocs({
@@ -7,15 +7,11 @@ export const docs = defineDocs({
77

88
export const blog = defineDocs({
99
dir: '../../content/blog',
10-
docs: {
11-
schema: (ctx) => {
12-
return ctx.schema.extend({
13-
author: z.string().optional(),
14-
date: z.string().date().or(z.date()).optional(),
15-
tags: z.array(z.string()).optional(),
16-
});
17-
},
18-
},
10+
schema: frontmatterSchema.extend({
11+
author: z.string().optional(),
12+
date: z.string().or(z.date()).optional(),
13+
tags: z.array(z.string()).optional(),
14+
}),
1915
}) as any;
2016

2117
export default defineConfig();

0 commit comments

Comments
 (0)