Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 8
Fix homepage font color and add blog section with in-depth analytical articles using fumadocs#36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
93865a1650c6f28e71906dee0344e1063f6929b5b35967a137f30e499bb056c526ccb0c70f4ca0115e54File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,200 @@ | ||
| import { notFound } from 'next/navigation'; | ||
| import { blog } from '@/app/source'; | ||
| import defaultMdxComponents from 'fumadocs-ui/mdx'; | ||
| import { HomeLayout } from 'fumadocs-ui/layouts/home'; | ||
| import { baseOptions } from '@/app/layout.config'; | ||
| import Link from 'next/link'; | ||
| import { ArrowLeft } from 'lucide-react'; | ||
| // Extended type for blog post data | ||
| interface BlogPostData { | ||
| title: string; | ||
| description?: string; | ||
| author?: string; | ||
| date?: string; | ||
| tags?: string[]; | ||
| body: React.ComponentType; | ||
| } | ||
| export default async function BlogPage({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ lang: string; slug?: string[] }>; | ||
| }) { | ||
| const { slug } = await params; | ||
| // If no slug, show blog index | ||
| if (!slug || slug.length === 0) { | ||
| const posts = blog.getPages(); | ||
| return ( | ||
| <HomeLayout {...baseOptions}> | ||
| <main className="container max-w-5xl mx-auto px-4 py-16"> | ||
| <div className="mb-12"> | ||
| <h1 className="text-4xl font-bold mb-4">Blog</h1> | ||
| <p className="text-lg text-fd-foreground/80"> | ||
| Insights, updates, and best practices from the ObjectStack team. | ||
| </p> | ||
| </div> | ||
| <div className="grid gap-8 md:grid-cols-2"> | ||
| {posts.map((post) => { | ||
| const postData = post.data as unknown as BlogPostData; | ||
| return ( | ||
| <Link | ||
| key={post.url} | ||
| href={post.url} | ||
| className="group block rounded-lg border border-fd-border bg-fd-card p-6 transition-all hover:border-fd-primary/30 hover:shadow-md" | ||
| > | ||
| <div className="mb-3"> | ||
| <h2 className="text-2xl font-semibold mb-2 group-hover:text-fd-primary transition-colors"> | ||
| {postData.title} | ||
| </h2> | ||
| {postData.description && ( | ||
| <p className="text-fd-foreground/70"> | ||
| {postData.description} | ||
| </p> | ||
| )} | ||
| </div> | ||
| <div className="flex items-center gap-4 text-sm text-fd-foreground/70"> | ||
| {postData.date && ( | ||
| <time dateTime={postData.date}> | ||
| {new Date(postData.date).toLocaleDateString('en-US', { | ||
| year: 'numeric', | ||
| month: 'long', | ||
| day: 'numeric', | ||
| })} | ||
| </time> | ||
| )} | ||
| {postData.author && ( | ||
| <span>By {postData.author}</span> | ||
| )} | ||
| </div> | ||
| {postData.tags && postData.tags.length > 0 && ( | ||
| <div className="mt-4 flex flex-wrap gap-2"> | ||
| {postData.tags.map((tag: string) => ( | ||
| <span | ||
| key={tag} | ||
| className="inline-flex items-center rounded-full bg-fd-primary/10 px-2.5 py-0.5 text-xs font-medium text-fd-primary" | ||
| > | ||
| {tag} | ||
| </span> | ||
| ))} | ||
| </div> | ||
| )} | ||
| </Link> | ||
| ); | ||
| })} | ||
| </div> | ||
| {posts.length === 0 && ( | ||
| <div className="text-center py-12"> | ||
| <p className="text-fd-foreground/70">No blog posts yet. Check back soon!</p> | ||
| </div> | ||
| )} | ||
| </main> | ||
| </HomeLayout> | ||
| ); | ||
| } | ||
| // Show individual blog post | ||
| const page = blog.getPage(slug); | ||
| if (!page) { | ||
| notFound(); | ||
| } | ||
| const pageData = page.data as unknown as BlogPostData; | ||
| const MDX = page.data.body; | ||
| return ( | ||
| <HomeLayout {...baseOptions}> | ||
| <main className="container max-w-4xl mx-auto px-4 py-16"> | ||
| <Link | ||
| href="/blog" | ||
| className="inline-flex items-center gap-2 text-sm text-fd-foreground/70 hover:text-fd-foreground mb-8 transition-colors" | ||
| > | ||
| <ArrowLeft className="w-4 h-4" /> | ||
| Back to Blog | ||
| </Link> | ||
| <article className="prose prose-neutral dark:prose-invert max-w-none"> | ||
| <header className="mb-8 pb-8 border-b border-fd-border"> | ||
| <h1 className="text-4xl font-bold mb-4">{pageData.title}</h1> | ||
| {pageData.description && ( | ||
| <p className="text-xl text-fd-foreground/80 mb-6"> | ||
| {pageData.description} | ||
| </p> | ||
| )} | ||
| <div className="flex items-center gap-4 text-sm text-fd-foreground/70"> | ||
| {pageData.date && ( | ||
| <time dateTime={pageData.date}> | ||
| {new Date(pageData.date).toLocaleDateString('en-US', { | ||
| year: 'numeric', | ||
| month: 'long', | ||
| day: 'numeric', | ||
| })} | ||
| </time> | ||
| )} | ||
| {pageData.author && ( | ||
| <span>By {pageData.author}</span> | ||
| )} | ||
| </div> | ||
| {pageData.tags && pageData.tags.length > 0 && ( | ||
| <div className="mt-4 flex flex-wrap gap-2"> | ||
| {pageData.tags.map((tag: string) => ( | ||
| <span | ||
| key={tag} | ||
| className="inline-flex items-center rounded-full bg-fd-primary/10 px-3 py-1 text-sm font-medium text-fd-primary" | ||
| > | ||
| {tag} | ||
| </span> | ||
| ))} | ||
| </div> | ||
| )} | ||
| </header> | ||
| <MDX components={defaultMdxComponents} /> | ||
| </article> | ||
| </main> | ||
| </HomeLayout> | ||
| ); | ||
| } | ||
| export async function generateStaticParams() { | ||
| return blog.getPages().map((page) => ({ | ||
| slug: page.slugs, | ||
| })); | ||
Comment on lines
+169
to
+172
CopilotAI | ||
| } | ||
| export async function generateMetadata({ | ||
| params, | ||
| }: { | ||
| params: Promise<{ slug?: string[] }>; | ||
Comment on lines
+175
to
+178
CopilotAI | ||
| }) { | ||
| const { slug } = await params; | ||
| // If no slug, return default metadata for blog index | ||
| if (!slug || slug.length === 0) { | ||
| return { | ||
| title: 'Blog', | ||
| description: 'Insights, updates, and best practices from the ObjectStack team.', | ||
| }; | ||
| } | ||
| const page = blog.getPage(slug); | ||
| if (!page) { | ||
| notFound(); | ||
| } | ||
| return { | ||
| title: page.data.title, | ||
| description: page.data.description, | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,14 @@ | ||
| import { docs } from 'fumadocs-mdx:collections/server'; | ||
| import { docs, blog as blogCollection } from 'fumadocs-mdx:collections/server'; | ||
| import { loader } from 'fumadocs-core/source'; | ||
| import { i18n } from '@/lib/i18n'; | ||
| export const source = loader({ | ||
| baseUrl: '/docs', | ||
| i18n, | ||
| source: (docs as any).toFumadocsSource(), | ||
| source: docs.toFumadocsSource(), | ||
| }); | ||
| export const blog = loader({ | ||
| baseUrl: '/blog', | ||
| source: blogCollection.toFumadocsSource(), | ||
| }); |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,7 +1,21 @@ | ||||||
| import { defineDocs, defineConfig } from 'fumadocs-mdx/config'; | ||||||
| import { defineDocs, defineConfig, frontmatterSchema } from 'fumadocs-mdx/config'; | ||||||
| import { z } from 'zod'; | ||||||
| export const docs = defineDocs({ | ||||||
| dir: '../../content/docs', | ||||||
| }) as any; | ||||||
| }); | ||||||
| const blogSchema = frontmatterSchema.extend({ | ||||||
| author: z.string().optional(), | ||||||
| date: z.coerce.string().optional(), | ||||||
CopilotAI | ||||||
| date: z.coerce.string().optional(), | |
| date: z.string().optional(), |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
CopilotAIJan 20, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
langparameter is extracted from params but never used in the BlogPage component. The component should either use this parameter for internationalization or remove it from the destructuring if it's not needed. Consider whether blog posts should support multiple languages.