From 3e0aeb1e1b809287fa2334a7fc87d75f0ae15cf4 Mon Sep 17 00:00:00 2001 From: David Dias Date: Mon, 10 Feb 2025 19:39:16 -0500 Subject: [PATCH] fix: breadcrumb bad path --- app/[lang]/[[...mdxPath]]/page.tsx | 91 +++++++++++++----------- app/[lang]/layout.tsx | 1 + app/_utils/generate-breadcrumb-schema.ts | 47 +++++++++++- 3 files changed, 96 insertions(+), 43 deletions(-) diff --git a/app/[lang]/[[...mdxPath]]/page.tsx b/app/[lang]/[[...mdxPath]]/page.tsx index 04ebbca..eb17984 100755 --- a/app/[lang]/[[...mdxPath]]/page.tsx +++ b/app/[lang]/[[...mdxPath]]/page.tsx @@ -1,6 +1,5 @@ /* eslint-disable react-hooks/rules-of-hooks -- false positive, useMDXComponents are not react hooks */ -import { BASE_URL } from '@/app/_constants/project' import { JsonLd, generateArticleSchema } from '@app/_components/json-ld' import { generateBreadcrumbSchema } from '@app/_utils/generate-breadcrumb-schema' import { Metadata } from 'next' @@ -9,38 +8,49 @@ import { useMDXComponents } from '../../../mdx-components' export const generateStaticParams = generateStaticParamsFor('mdxPath') -export async function generateMetadata(props: PageProps): Promise { - try { - const params = await props.params - const { metadata } = await importPage(params.mdxPath || [], params.lang) - const isHomepage = !params.mdxPath || params.mdxPath.length === 0 +export async function generateMetadata({ params }): Promise { + const { frontMatter, metadata } = await importPage(params.mdxPath || [], params.lang) + const isHomepage = !params.mdxPath || params.mdxPath.length === 0 + const baseUrl = 'https://uxpatterns.dev' + const path = params.mdxPath?.join('/') || '' - // Include the language prefix in the canonical path - const canonicalPath = `/${params.lang}${params.mdxPath ? `/${params.mdxPath.join('/')}` : ''}` + // Shared metadata + const title = frontMatter?.title || metadata?.title || 'UX Patterns for Devs' + const description = frontMatter?.description || metadata?.description || '' + const summary = frontMatter?.summary || '' - const ogImage = { - url: isHomepage - ? '/og/opengraph-image.png' - : `/api/og?title=${encodeURIComponent(metadata.title || '')}`, - width: 1200, - height: 630, - type: 'image/png', - } + // OG image handling + const isPatternPage = params.mdxPath?.[0] === 'patterns' + const patternName = isPatternPage ? params.mdxPath?.[params.mdxPath.length - 1] : null + const ogImageUrl = isHomepage + ? '/og/opengraph-image.png' + : `/api/og?title=${encodeURIComponent(title)}` - return { - ...metadata, - openGraph: { - ...metadata.openGraph, - images: [ogImage], - url: `${BASE_URL}${canonicalPath}` - }, - alternates: { - canonical: `${BASE_URL}${canonicalPath}` - } - } - } catch (e) { - console.error('Error generating metadata:', e) - return null + return { + title, + description, + openGraph: { + title, + description, + type: 'article', + images: [ + ...(isPatternPage && patternName ? [{ + url: `/covers/patterns/${patternName}.png`, + width: 800, + height: 400, + alt: `${summary} - UX Pattern`, + }] : []), + { + url: ogImageUrl, + width: 1200, + height: 630, + alt: description || title, + }, + ], + }, + alternates: { + canonical: `${baseUrl}/${params.lang}${path ? `/${path}` : ''}`, + }, } } @@ -55,26 +65,25 @@ const Wrapper = useMDXComponents().wrapper export default async function Page(props: PageProps) { const params = await props.params - const result = await importPage(params.mdxPath, params.lang) - const { default: MDXContent, toc, metadata } = result + const { default: MDXContent, toc, metadata } = await importPage(params.mdxPath, params.lang) - // Get the OG image URL from metadata + // Use the same logic for OG image as in generateMetadata const isHomepage = !params.mdxPath || params.mdxPath.length === 0 + const title = metadata?.title || 'UX Patterns for Devs' + const description = metadata?.description || '' const ogImageUrl = isHomepage ? '/og/opengraph-image.png' - : `/api/og?title=${encodeURIComponent(metadata.title || '')}` + : `/api/og?title=${encodeURIComponent(title)}` const schemaData = generateArticleSchema( - metadata.title || '', - metadata.description || '', + title, + description, `/${params.lang}/${params.mdxPath?.join('/') || ''}`, ogImageUrl ) - // Generate breadcrumb items - const breadcrumbs = [ - { title: 'Home', url: `/${params.lang}` } - ] + // Generate breadcrumb items for patterns only + const breadcrumbs: Array<{ title: string; url: string }> = [] if (params.mdxPath) { let currentPath = '' @@ -93,7 +102,7 @@ export default async function Page(props: PageProps) { return ( <> - + {breadcrumbs.length > 0 && }
diff --git a/app/[lang]/layout.tsx b/app/[lang]/layout.tsx index 8b7adbe..927e049 100755 --- a/app/[lang]/layout.tsx +++ b/app/[lang]/layout.tsx @@ -77,6 +77,7 @@ export default async function RootLayout({ children, params }: { + crumb.url)?.url?.match(/^\/([a-z]{2})\//) + const lang = langMatch ? langMatch[1] : 'en' // Default to 'en' if no language found + + // Ensure home page is always first in the breadcrumb with correct language + const homePage: BreadcrumbItem = { + title: 'Home', + url: `/${lang}` + } + + // Filter breadcrumbs based on the section + const filteredBreadcrumbs = breadcrumbs.reduce((acc: BreadcrumbItem[], crumb, index) => { + // Always keep the main section (patterns, pattern-guide, etc.) + if (crumb.url?.match(/^\/[a-z]{2}\/[^/]+$/)) { + acc.push(crumb) + return acc + } + + // For the patterns section, skip category level + if (crumb.url?.includes('/patterns/')) { + // If it's the last item, keep it + if (index === breadcrumbs.length - 1) { + acc.push(crumb) + } + return acc + } + + // For all other sections, keep everything + acc.push(crumb) + return acc + }, []) + + // Combine home page with filtered breadcrumbs + const fullBreadcrumbs = [homePage, ...filteredBreadcrumbs] + + if (fullBreadcrumbs.length === 0) { + return { + "@context": "https://schema.org", + "@type": "BreadcrumbList", + "itemListElement": [] + } + } + return { "@context": "https://schema.org", "@type": "BreadcrumbList", - "itemListElement": breadcrumbs.map((crumb, index) => { - const isLastItem = index === breadcrumbs.length - 1 + "itemListElement": fullBreadcrumbs.map((crumb, index) => { + const isLastItem = index === fullBreadcrumbs.length - 1 const baseItem = { "@type": "ListItem", "position": index + 1,