import { CtaBanner } from '@/app/(home)/_sections/cta-banner'; import { HeroContainer } from '@/app/(home)/_sections/hero'; import { Testimonials } from '@/app/(home)/_sections/testimonials'; import { FeatureCardContainer } from '@/components/feature-card'; import { GetStartedButton } from '@/components/get-started-button'; import { GuideCard } from '@/components/guide-card'; import { Logo } from '@/components/logo'; import { SectionHeader } from '@/components/section'; import { Toc } from '@/components/toc'; import { url, getAuthor } from '@/lib/layout.shared'; import { getOgImageUrl, getPageMetadata } from '@/lib/metadata'; import { guideSource } from '@/lib/source'; import { getMDXComponents } from '@/mdx-components'; import { ArrowLeftIcon, ClockIcon } from 'lucide-react'; import type { Metadata } from 'next'; import Image from 'next/image'; import Link from 'next/link'; import { notFound } from 'next/navigation'; import Script from 'next/script'; const difficultyColors = { beginner: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', intermediate: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', advanced: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200', }; const difficultyLabels = { beginner: 'Beginner', intermediate: 'Intermediate', advanced: 'Advanced', }; export async function generateStaticParams() { const guides = await guideSource.getPages(); return guides.map((guide) => { // Extract slug from URL (e.g., '/guides/my-guide' -> 'my-guide') const slug = guide.url.replace(/^\/guides\//, '').replace(/\/$/, ''); return { guideSlug: slug }; }); } export async function generateMetadata({ params, }: { params: Promise<{ guideSlug: string }>; }): Promise { const { guideSlug } = await params; const guide = await guideSource.getPage([guideSlug]); if (!guide) { return { title: 'Guide Not Found', }; } return getPageMetadata({ title: guide.data.title, description: guide.data.description, url: url(guide.url), image: getOgImageUrl(guide.url), }); } export default async function Page({ params, }: { params: Promise<{ guideSlug: string }>; }) { const { guideSlug } = await params; const guide = await guideSource.getPage([guideSlug]); const Body = guide?.data.body; const author = getAuthor(guide?.data.team); const goBackUrl = '/guides'; const relatedGuides = (await guideSource.getPages()) .filter( (item) => item.data.difficulty === guide?.data.difficulty && item.url !== guide?.url, ) .sort((a, b) => b.data.date.getTime() - a.data.date.getTime()) .slice(0, 3); if (!Body) { return notFound(); } const slug = guide.url.replace(/^\/guides\//, '').replace(/\/$/, ''); // Create the HowTo JSON-LD schema const jsonLd = { '@context': 'https://schema.org', '@type': 'HowTo', name: guide?.data.title, description: guide?.data.description, totalTime: `PT${guide?.data.timeToComplete}M`, step: guide?.data.steps.map((step, i) => ({ '@type': 'HowToStep', position: i + 1, name: step.name, url: url(`/guides/${slug}#${step.anchor}`), })), }; return (
Back to all guides
{author?.image ? ( {author.name} ) : ( )}

{author?.name || 'OpenPanel Team'}

{guide?.data.date.toLocaleDateString()}

{guide?.data.updated && (

Updated on {guide?.data.updated.toLocaleDateString()}

)}
{difficultyLabels[guide?.data.difficulty || 'beginner']}
{guide?.data.timeToComplete} min