# seo Wpina `@payloadcms/plugin-seo` (pola meta w kolekcjach) i dodaje warstwę logiki: składanie tytułów, budowanie `Metadata` dla Next.js z canonical i hreflang, auto-fill pustych meta z treści dokumentu. ## Zależność Dodaj do `dependencies` (pin do wersji payload): ```json "dependencies": { "@payloadcms/plugin-seo": "3.84.1" } ``` ## Config (payload.config.ts) ```ts ipalKit({ seo: { collections: ['pages', 'posts'], // które kolekcje dostają meta // generateTitle: ({ doc }) => `${doc.title}`, // opcjonalne // generateDescription: ({ doc }) => doc.excerpt ?? '', // fields: [...], // extra pola w grupie SEO // autoFill: { title: 'title', description: 'excerpt' }, // mapowanie auto-fill // autoFill: false, // wyłącz auto-fill }, }) ``` Dodaje tab **SEO** (title, description, image) do wskazanych kolekcji. Auto-fill: hook `beforeChange` wypełnia puste `meta.title` z pola dokumentu (domyślnie `title`). Nigdy nie nadpisuje tego, co edytor wpisał ręcznie. ## Front — generateMetadata (factory) Najprościej: factory redukuje boilerplate. Klient podaje resolvery (bo zna swoje kolekcje/routing), plugin składa metadata. ```ts // app/(frontend)/[locale]/[[...segments]]/page.tsx import { createMetadataGenerator } from 'ipal-kit' import { getSiteSettings } from 'ipal-kit' const gen = createMetadataGenerator({ config: i18nConfig, baseUrl: process.env.NEXT_PUBLIC_SERVER_URL, resolveDocument: async ({ payload, params, locale }) => { const slug = /* z params */ '' const res = await payload.find({ collection: 'pages', where: { slug: { equals: slug } }, locale: 'all', depth: 1, limit: 1, }) return res.docs[0] ?? null // musi mieć .meta i .slug (locale:'all') }, resolveSiteName: async ({ payload, locale }) => (await getSiteSettings(payload, { locale })).siteName ?? null, }) export async function generateMetadata({ params }) { const payload = await getPayload({ config }) const { locale } = await params return gen({ payload, params: await params, locale }) } ``` ## Front — niżej: buildMetadata bezpośrednio Jeśli chcesz pełną kontrolę zamiast factory: ```ts import { buildMetadata, getLocalizedSlugs } from 'ipal-kit' const doc = await payload.findByID({ collection: 'pages', id, locale: 'all', depth: 1 }) return buildMetadata({ meta: doc.meta, // z plugin-seo siteName: settings.siteName, imageUrl: /* url OG image */, locale: 'pl', slugs: getLocalizedSlugs({ slugField: doc.slug, config }), config, baseUrl: 'https://example.com', }) // → { title, description, openGraph, alternates: { canonical, languages } } ``` ## Pomocnicze ```ts import { composeTitle, buildHreflangAlternates } from 'ipal-kit' composeTitle({ pageTitle: 'O nas', siteName: 'Acme' }) // 'O nas | Acme' buildHreflangAlternates({ slugs, config, baseUrl }) // { pl: '...', en: '...' } ```