feat(seo): configure payload seo plugin, add dynamic metadata generation and inject LocalBusiness json-ld schema
This commit is contained in:
@@ -11,6 +11,57 @@ import type { Page } from '../../../../payload-types'
|
|||||||
|
|
||||||
type FAQFullBlock = Extract<NonNullable<Page['layout']>[0], { blockType: 'faqFull' }>
|
type FAQFullBlock = Extract<NonNullable<Page['layout']>[0], { blockType: 'faqFull' }>
|
||||||
|
|
||||||
|
import type { Metadata } from 'next'
|
||||||
|
|
||||||
|
export async function generateMetadata(
|
||||||
|
props: { params: Promise<{ locale: string; slug?: string[] }> }
|
||||||
|
): Promise<Metadata> {
|
||||||
|
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: {
|
export default async function Page(props: {
|
||||||
params: Promise<{ locale: string; slug?: string[] }>
|
params: Promise<{ locale: string; slug?: string[] }>
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -58,8 +58,25 @@ export default async function RootLayout(props: {
|
|||||||
// CMS: fallback to generic link if no number is configured.
|
// CMS: fallback to generic link if no number is configured.
|
||||||
const whatsappLink = companyData?.whatsappNumber ? `https://wa.me/${companyData.whatsappNumber}` : '#';
|
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 (
|
return (
|
||||||
<html lang={locale} suppressHydrationWarning className={`${publicSans.variable} ${fraunces.variable}`}>
|
<html lang={locale} suppressHydrationWarning className={`${publicSans.variable} ${fraunces.variable}`}>
|
||||||
|
<head>
|
||||||
|
<script
|
||||||
|
type="application/ld+json"
|
||||||
|
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaOrg) }}
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
<body className="font-sans antialiased bg-background text-foreground min-h-screen flex flex-col">
|
<body className="font-sans antialiased bg-background text-foreground min-h-screen flex flex-col">
|
||||||
<Providers>
|
<Providers>
|
||||||
<LanguageSlugsProvider>
|
<LanguageSlugsProvider>
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ import { StrikethroughFeatureClient as StrikethroughFeatureClient_e70f5e05f09f93
|
|||||||
import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
import { UnderlineFeatureClient as UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||||
import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
import { BoldFeatureClient as BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||||
import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
import { ItalicFeatureClient as ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864 } from '@payloadcms/richtext-lexical/client'
|
||||||
|
import { OverviewComponent as OverviewComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
|
||||||
|
import { MetaTitleComponent as MetaTitleComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
|
||||||
|
import { MetaDescriptionComponent as MetaDescriptionComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
|
||||||
|
import { MetaImageComponent as MetaImageComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
|
||||||
|
import { PreviewComponent as PreviewComponent_a8a977ebc872c5d5ea7ee689724c0860 } from '@payloadcms/plugin-seo/client'
|
||||||
import { MaskedField as MaskedField_630f031911beff97691b963f10a20a86 } from '@intecion/ipal-kit/client'
|
import { MaskedField as MaskedField_630f031911beff97691b963f10a20a86 } from '@intecion/ipal-kit/client'
|
||||||
import { TestEmailButton as TestEmailButton_630f031911beff97691b963f10a20a86 } from '@intecion/ipal-kit/client'
|
import { TestEmailButton as TestEmailButton_630f031911beff97691b963f10a20a86 } from '@intecion/ipal-kit/client'
|
||||||
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'
|
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'
|
||||||
@@ -50,6 +55,11 @@ export const importMap = {
|
|||||||
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
"@payloadcms/richtext-lexical/client#UnderlineFeatureClient": UnderlineFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||||
"@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
"@payloadcms/richtext-lexical/client#BoldFeatureClient": BoldFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||||
"@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
"@payloadcms/richtext-lexical/client#ItalicFeatureClient": ItalicFeatureClient_e70f5e05f09f93e00b997edb1ef0c864,
|
||||||
|
"@payloadcms/plugin-seo/client#OverviewComponent": OverviewComponent_a8a977ebc872c5d5ea7ee689724c0860,
|
||||||
|
"@payloadcms/plugin-seo/client#MetaTitleComponent": MetaTitleComponent_a8a977ebc872c5d5ea7ee689724c0860,
|
||||||
|
"@payloadcms/plugin-seo/client#MetaDescriptionComponent": MetaDescriptionComponent_a8a977ebc872c5d5ea7ee689724c0860,
|
||||||
|
"@payloadcms/plugin-seo/client#MetaImageComponent": MetaImageComponent_a8a977ebc872c5d5ea7ee689724c0860,
|
||||||
|
"@payloadcms/plugin-seo/client#PreviewComponent": PreviewComponent_a8a977ebc872c5d5ea7ee689724c0860,
|
||||||
"@intecion/ipal-kit/client#MaskedField": MaskedField_630f031911beff97691b963f10a20a86,
|
"@intecion/ipal-kit/client#MaskedField": MaskedField_630f031911beff97691b963f10a20a86,
|
||||||
"@intecion/ipal-kit/client#TestEmailButton": TestEmailButton_630f031911beff97691b963f10a20a86,
|
"@intecion/ipal-kit/client#TestEmailButton": TestEmailButton_630f031911beff97691b963f10a20a86,
|
||||||
"@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1
|
"@payloadcms/next/rsc#CollectionCards": CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1
|
||||||
|
|||||||
@@ -532,6 +532,14 @@ export interface Page {
|
|||||||
}
|
}
|
||||||
)[]
|
)[]
|
||||||
| null;
|
| null;
|
||||||
|
meta?: {
|
||||||
|
title?: string | null;
|
||||||
|
description?: string | null;
|
||||||
|
/**
|
||||||
|
* Maximum upload file size: 12MB. Recommended file size for images is <500KB.
|
||||||
|
*/
|
||||||
|
image?: (number | null) | Media;
|
||||||
|
};
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
@@ -1217,6 +1225,13 @@ export interface PagesSelect<T extends boolean = true> {
|
|||||||
blockName?: T;
|
blockName?: T;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
meta?:
|
||||||
|
| T
|
||||||
|
| {
|
||||||
|
title?: T;
|
||||||
|
description?: T;
|
||||||
|
image?: T;
|
||||||
|
};
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { sqliteAdapter } from '@payloadcms/db-sqlite'
|
import { sqliteAdapter } from '@payloadcms/db-sqlite'
|
||||||
import { mcpPlugin } from '@payloadcms/plugin-mcp'
|
import { mcpPlugin } from '@payloadcms/plugin-mcp'
|
||||||
|
import { seoPlugin } from '@payloadcms/plugin-seo'
|
||||||
import { ipalKit, panelSmtpAdapter } from '@intecion/ipal-kit'
|
import { ipalKit, panelSmtpAdapter } from '@intecion/ipal-kit'
|
||||||
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
import { lexicalEditor } from '@payloadcms/richtext-lexical'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
@@ -46,6 +47,12 @@ export default buildConfig({
|
|||||||
email: panelSmtpAdapter(),
|
email: panelSmtpAdapter(),
|
||||||
plugins: [
|
plugins: [
|
||||||
mcpPlugin({}),
|
mcpPlugin({}),
|
||||||
|
seoPlugin({
|
||||||
|
collections: ['pages'],
|
||||||
|
uploadsCollection: 'media',
|
||||||
|
generateTitle: ({ doc }) => `Finanz- & Lohnbuchhaltung Dipl.-Ing. Janusz Kędzierski — ${doc.title}`,
|
||||||
|
generateDescription: ({ doc }) => doc.excerpt || '',
|
||||||
|
}),
|
||||||
ipalKit({
|
ipalKit({
|
||||||
i18n,
|
i18n,
|
||||||
pages: { slug: 'pages' },
|
pages: { slug: 'pages' },
|
||||||
|
|||||||
Reference in New Issue
Block a user