SEO: multilingual homepage (homeSlug per locale in path/sitemap), Article + llms.txt generators, siteDescription fallback, composeTitle dedup

This commit is contained in:
2026-09-15 21:55:48 +02:00
parent 671090b1b1
commit 7ee60e7313
4 changed files with 66 additions and 20 deletions
+30 -5
View File
@@ -181,7 +181,10 @@ export function createContentHelpers({
* Handles automatically:
* - pages collection + content collections (with their archive prefix)
* - excludes the homepage (maps to { slug: [] } — the root)
* - excludes drafts, 404/500/system slugs, and meta.noindex docs
* - excludes drafts and 404/500/system slugs
* - KEEPS noindex pages (they must still render — noindex controls indexing,
* not existence; skipping them would force dynamic rendering)
* - localized slugs (string or per-locale map) both handled
* - single-locale → { slug }[]; multi-locale → { locale, slug }[]
*
* Wire it in the project:
@@ -205,12 +208,15 @@ export function createContentHelpers({
const params: Array<{ locale: string; slug: string[] } | { slug: string[] }> = []
for (const locale of locales) {
// NO where:{_status} filter — collections without drafts enabled don't
// register the _status field, and querying it throws
// "path cannot be queried: _status". We filter drafts in memory below,
// which is safe for every collection (with or without drafts).
const result = await payload.find({
collection: pagesSlug as never,
depth: 0,
limit: 1000,
locale: (locale ?? 'all') as never,
where: { _status: { not_equals: 'draft' } } as never,
})
for (const raw of result.docs as Array<{
@@ -219,8 +225,16 @@ export function createContentHelpers({
meta?: { noindex?: boolean } | null
slug?: unknown
}>) {
// Draft filter in memory (safe whether or not the collection has drafts).
if (raw._status && raw._status !== 'published') {continue}
if (raw.meta?.noindex) {continue}
// NOTE: unlike the sitemap, we do NOT skip meta.noindex here. A noindex
// page (privacy, cookies, terms) still needs to render — users reach it
// from the footer and crawlers read its <meta robots=noindex>. Pre-render
// it as SSG so it's fast and its <head> is complete; noindex controls
// INDEXING, not whether the page exists. Skipping it would force dynamic
// rendering (the very streaming problem we're avoiding).
if (homeId && raw.id === homeId) {
// Homepage → root. Emit an empty-slug param so '/' (or '/pl') builds.
const empty = singleLocale ? { slug: [] } : { slug: [], locale: locale as string }
@@ -228,11 +242,22 @@ export function createContentHelpers({
continue
}
const slug = typeof raw.slug === 'string' ? raw.slug : undefined
// Slug may be a plain string OR a localized map ({ pl: 'kontakt' }) when
// read with locale:'all' or left unflattened. Handle both, or localized
// pages get silently dropped.
const rawSlug = raw.slug
const slug =
typeof rawSlug === 'string'
? rawSlug
: rawSlug && typeof rawSlug === 'object'
? (((rawSlug as Record<string, unknown>)[locale ?? ''] as string | undefined) ??
(Object.values(rawSlug as Record<string, unknown>)[0] as string | undefined))
: undefined
if (!slug || EXCLUDED.has(slug)) {continue}
// Multi-level slugs ('atrakcje/telefon') → array segments.
const segments = slug.split('/').filter(Boolean)
const segments = String(slug).split('/').filter(Boolean)
params.push(
singleLocale ? { slug: segments } : { slug: segments, locale: locale as string },
)