From 12126a0920d684c4ee496469b478b425beec5ed7 Mon Sep 17 00:00:00 2001 From: MichalC <103170279+wSBMichalee@users.noreply.github.com> Date: Fri, 18 Sep 2026 00:05:02 +0200 Subject: [PATCH] feat(seo): configure payload seo plugin, add dynamic metadata generation and inject LocalBusiness json-ld schema --- .../(frontend)/[locale]/[[...slug]]/page.tsx | 51 +++++++++++++++++++ src/app/(frontend)/[locale]/layout.tsx | 17 +++++++ src/app/(payload)/admin/importMap.js | 10 ++++ src/payload-types.ts | 15 ++++++ src/payload.config.ts | 7 +++ 5 files changed, 100 insertions(+) diff --git a/src/app/(frontend)/[locale]/[[...slug]]/page.tsx b/src/app/(frontend)/[locale]/[[...slug]]/page.tsx index ca31af3..3aa9f11 100644 --- a/src/app/(frontend)/[locale]/[[...slug]]/page.tsx +++ b/src/app/(frontend)/[locale]/[[...slug]]/page.tsx @@ -11,6 +11,57 @@ import type { Page } from '../../../../payload-types' type FAQFullBlock = Extract[0], { blockType: 'faqFull' }> +import type { Metadata } from 'next' + +export async function generateMetadata( + props: { params: Promise<{ locale: string; slug?: string[] }> } +): Promise { + const params = await props.params + const { locale, slug } = params + const payload = await getPayload({ config: configPromise }) + + const route = await resolveRoute({ payload, locale, segments: slug || [] }) + if (!route) { + return {} + } + + const doc = await payload.findByID({ + collection: ('collection' in route ? route.collection : 'pages') as 'pages', + id: route.doc.id as string, + locale: locale as 'all' | 'pl' | 'de', + }) + + // Retrieve meta object injected by @payloadcms/plugin-seo + const meta = (doc as any).meta + + const title = meta?.title || doc.title + const description = meta?.description || '' + + let imageUrl = '' + if (meta?.image && typeof meta.image === 'object' && meta.image.url) { + imageUrl = meta.image.url + } else if (meta?.image && typeof meta.image === 'string') { + // If it's just an ID, we might not have the full URL without a separate fetch, + // but usually Payload plugin-seo populates the image relationship. + } + + return { + title, + description, + openGraph: { + title, + description, + ...(imageUrl && { images: [{ url: imageUrl }] }), + }, + twitter: { + card: 'summary_large_image', + title, + description, + ...(imageUrl && { images: [imageUrl] }), + }, + } +} + export default async function Page(props: { params: Promise<{ locale: string; slug?: string[] }> }) { diff --git a/src/app/(frontend)/[locale]/layout.tsx b/src/app/(frontend)/[locale]/layout.tsx index 84b2c7d..4cbc38d 100644 --- a/src/app/(frontend)/[locale]/layout.tsx +++ b/src/app/(frontend)/[locale]/layout.tsx @@ -58,8 +58,25 @@ export default async function RootLayout(props: { // CMS: fallback to generic link if no number is configured. const whatsappLink = companyData?.whatsappNumber ? `https://wa.me/${companyData.whatsappNumber}` : '#'; + const schemaOrg = { + "@context": "https://schema.org", + "@type": "AccountingService", + "name": "Finanz- & Lohnbuchhaltung Dipl.-Ing. Janusz Kędzierski", + "telephone": companyData?.phone || '', + "email": companyData?.email || '', + "address": companyData?.address || '', + "openingHours": companyData?.hours || '', + "url": "https://www.jkedzierski.de" + }; + return ( + +