import AllPostsButton from '@/components/blog/all-posts-button'; import BlogGrid from '@/components/blog/blog-grid'; import { getMDXComponents } from '@/components/docs/mdx-components'; import { NewsletterCard } from '@/components/newsletter/newsletter-card'; import { PremiumBadge } from '@/components/premium/premium-badge'; import { PremiumGuard } from '@/components/premium/premium-guard'; import { websiteConfig } from '@/config/website'; import { LocaleLink } from '@/i18n/navigation'; import { formatDate } from '@/lib/formatter'; import { constructMetadata } from '@/lib/metadata'; import { checkPremiumAccess } from '@/lib/premium-access'; import { getSession } from '@/lib/server'; import { type BlogType, authorSource, blogSource, categorySource, } from '@/lib/source'; import { getUrlWithLocale } from '@/lib/urls/urls'; import { InlineTOC } from 'fumadocs-ui/components/inline-toc'; import { CalendarIcon, FileTextIcon } from 'lucide-react'; import type { Metadata } from 'next'; import type { Locale } from 'next-intl'; import { getTranslations } from 'next-intl/server'; import Image from 'next/image'; import { notFound } from 'next/navigation'; import '@/styles/mdx.css'; /** * get related posts, random pick from all posts with same locale, different slug, * max size is websiteConfig.blog.relatedPostsSize */ async function getRelatedPosts(post: BlogType) { const relatedPosts = blogSource .getPages(post.locale) .filter((p) => p.data.published) .filter((p) => p.slugs.join('/') !== post.slugs.join('/')) .sort(() => Math.random() - 0.5) .slice(0, websiteConfig.blog.relatedPostsSize); return relatedPosts; } export function generateStaticParams() { return blogSource .getPages() .filter((post) => post.data.published) .flatMap((post) => { return { locale: post.locale, slug: post.slugs, }; }); } export async function generateMetadata({ params, }: BlogPostPageProps): Promise { const { locale, slug } = await params; const post = blogSource.getPage(slug, locale); if (!post) { notFound(); } const t = await getTranslations({ locale, namespace: 'Metadata' }); return constructMetadata({ title: `${post.data.title} | ${t('title')}`, description: post.data.description, canonicalUrl: getUrlWithLocale(`/blog/${slug}`, locale), image: post.data.image, }); } interface BlogPostPageProps { params: Promise<{ locale: Locale; slug: string[]; }>; } export default async function BlogPostPage(props: BlogPostPageProps) { const { locale, slug } = await props.params; const post = blogSource.getPage(slug, locale); if (!post) { notFound(); } const { date, title, description, image, author, categories, premium } = post.data; const publishDate = formatDate(new Date(date)); const blogAuthor = authorSource.getPage([author], locale); const blogCategories = categorySource .getPages(locale) .filter((category) => categories.includes(category.slugs[0] ?? '')); // Check premium access for premium posts const session = await getSession(); const hasPremiumAccess = premium && session?.user?.id ? await checkPremiumAccess(session.user.id) : !premium; // Non-premium posts are always accessible const MDX = post.data.body; // getTranslations may cause error DYNAMIC_SERVER_USAGE, so we set dynamic to force-static const t = await getTranslations('BlogPage'); // get related posts const relatedPosts = await getRelatedPosts(post); return (
{/* content section */}
{/* left column (blog post content) */}
{/* Basic information */}
{/* blog post image */}
{image && ( {title )}
{/* blog post date and premium badge */}
{publishDate}
{premium && }
{/* blog post title */}

{title}

{/* blog post description */}

{description}

{/* blog post content */} {/* in order to make the mdx.css work, we need to add the className prose to the div */} {/* https://github.com/tailwindlabs/tailwindcss-typography */}
{/* right column (sidebar) */}
{/* author info */} {blogAuthor && (

{t('author')}

{blogAuthor.data.avatar && ( {`avatar )}
{blogAuthor.data.name}
)} {/* categories */}

{t('categories')}

    {blogCategories.map( (category) => category && (
  • {category.data.name}
  • ) )}
{/* table of contents */}
{post.data.toc && ( )}
{/* Footer section shows related posts */} {relatedPosts && relatedPosts.length > 0 && (

{t('morePosts')}

)} {/* newsletter */}
); }