# pages System pages: centralne przypisanie ról (homepage / privacyPolicy / cookiePolicy) do dokumentów z klienckiej kolekcji Pages, w SiteSettings. Front rozwiązuje rolę na ścieżkę locale-aware. ## Config (payload.config.ts) ```ts ipalKit({ pages: { slug: 'pages', // slug Twojej kolekcji Pages // roles: ['homepage', 'privacyPolicy', 'cookiePolicy'], // domyślnie wszystkie }, }) ``` Dodaje tab **System Pages** w SiteSettings z polami relationship do Twojej kolekcji Pages. Edytor wybiera, który dokument pełni którą rolę. Plugin nie zna Twojej kolekcji — slug podajesz w opcji. ## Front — rozwiązanie ścieżki roli ```ts import { getSystemPagePath } from '@intecion/ipal-kit' // SiteSettings z locale:'all' + depth:1 (żeby relationship był obiektem, nie ID) const settings = await payload.findGlobal({ slug: 'site-settings', locale: 'all', depth: 1, }) // homepage w locale 'pl' getSystemPagePath({ page: settings.homepage, locale: 'pl', config }) // → '/pl' (home slug zwija się do roota) lub '/pl/strona-glowna' // polityka prywatności w 'en' getSystemPagePath({ page: settings.privacyPolicy, locale: 'en', config }) // → '/en/privacy-policy' ``` Zwraca `undefined`, gdy rola nieprzypisana lub dokument nie ma slug w danym locale — caller decyduje o fallbacku (404, redirect). ## Typowy przypadek: `/{locale}` → strona główna W trasie `[locale]/page.tsx` czytasz `settings.homepage`, bierzesz jego slug w bieżącym locale i renderujesz ten dokument. Link do polityki prywatności w stopce / bannerze cookies bierzesz z `getSystemPagePath({ role: privacyPolicy })`. ## Role ```ts import { ALL_SYSTEM_PAGE_ROLES } from '@intecion/ipal-kit' // ['homepage', 'privacyPolicy', 'cookiePolicy'] ``` ## KRYTYCZNE: generateStaticParams dla [[...slug]] (SEO + head) Trasa `[[...slug]]` (opcjonalny catch-all) BEZ `generateStaticParams` jest przez Next traktowana jako **dynamiczna** (`ƒ Dynamic`). W trybie dynamicznym z React 19 serwer wysyła pusty `
`, a metadata streamuje na końcu `` — crawlery (Lighthouse, Screaming Frog) nie widzą `` w head → SEO spada. **Z `generateStaticParams` trasa kompiluje się jako SSG (`●`)** → synchroniczny, kompletny `` → SEO 100/100. To najsilniejsze rozwiązanie problemu metadata-w-head (mocniejsze niż samo ISR/htmlLimitedBots). Plugin dostarcza gotowy generateStaticParams przez createContentHelpers: ```ts // lib/content.ts — dodaj do destrukturyzacji export const { getCachedPayload, getSettings, resolveRoute, generateStaticParams, // ← z pluginu sitemap, robots, } = createContentHelpers({ config, content: contentConfig, i18n: i18nConfig, baseUrl }) // app/(frontend)/[[...slug]]/page.tsx (albo [locale]/[[...slug]]) export { generateStaticParams } from '@/lib/content' ``` Helper automatycznie: pobiera pages + kolekcje treści, wyklucza homepage (→ root), drafty, 404/500, noindex; zwraca `{slug}[]` (jednojęzyczny) albo `{locale, slug}[]` (wielojęzyczny). Obsługuje slugi wielopoziomowe (`a/b` → `['a','b']`). ## PUŁAPKA: await searchParams deoptymalizuje ISR W Next 15/16 `searchParams` to Promise. Odczyt `const { page } = await searchParams` w komponencie strony **deoptymalizuje ISR** — wymusza dynamiczne renderowanie dla tego requestu (traci cały zysk SSG/ISR + wraca problem metadata w body). - **Strona BEZ paginacji** → NIE przekazuj/nie czytaj `searchParams` wcale: ```ts export default async function Page({ params }) { // bez searchParams const { locale, slug } = await params const route = await resolveRoute(locale, slug ?? []) // bez page } ``` - **Strona Z paginacją** (archiwum) → czytaj searchParams, ale świadomie (ta trasa będzie dynamiczna). Rozważ osobną trasę dla archiwum z paginacją, żeby zwykłe strony zostały SSG. Reguła: `searchParams` tylko tam, gdzie NAPRAWDĘ potrzebujesz (paginacja). Wszędzie indziej pomiń — inaczej tracisz SSG i SEO.