Files
2026-09-28 19:44:28 +02:00

103 lines
3.4 KiB
JavaScript

import { buildLocalizedPath } from '../i18n/index.js';
import { composeTitle } from './composeTitle.js';
import { buildHreflangAlternates } from './hreflang.js';
/**
* Assembles a Next.js-compatible Metadata object from document SEO fields and
* site-level data. Locale-aware: canonical points at the current locale's
* path, and hreflang alternates cover every locale the document exists in.
*
* Designed for use inside Next.js `generateMetadata`. The caller resolves the
* pieces (meta group, site name, image URL, localized slugs) and passes them
* in — the plugin composes, it doesn't fetch.
*/ export function buildMetadata({ baseUrl, config, homeSlug = 'home', imageUrl, locale, meta, order, pageTitle, prefix, query, separator, siteDescription, siteName, slugs }) {
// Title source priority: titleOverride (exact, wins outright) > meta.title
// (SEO title an editor set) > pageTitle (the document's own name). This means
// a page with no SEO title still shows its name (e.g. 'Sprzątanie biur')
// composed with the site name, instead of just the site name or a blank.
const override = meta?.titleOverride?.trim();
const resolvedPageTitle = meta?.title?.trim() || pageTitle?.trim() || undefined;
const title = override || composeTitle({
order,
pageTitle: resolvedPageTitle,
separator,
siteName
});
const description = meta?.description?.trim() || siteDescription?.trim() || undefined;
const origin = baseUrl?.replace(/\/$/, '') ?? '';
const suffix = query ?? '';
const currentPath = buildLocalizedPath({
config,
homeSlug,
locale,
prefix,
slugs
});
const canonical = currentPath ? `${origin}${currentPath}${suffix}` : undefined;
const languages = buildHreflangAlternates({
baseUrl,
config,
homeSlug,
prefix,
slugs
});
if (suffix) {
for (const code of Object.keys(languages)){
languages[code] = `${languages[code]}${suffix}`;
}
}
const images = imageUrl ? [
{
url: imageUrl
}
] : undefined;
return {
title,
...description && {
description
},
alternates: {
...canonical && {
canonical
},
...Object.keys(languages).length > 0 && {
languages
}
},
openGraph: {
title,
...description && {
description
},
...images && {
images
},
type: 'website',
locale,
...siteName ? {
siteName
} : {},
...canonical ? {
url: canonical
} : {}
},
// robots: noindex pages are excluded (follow keeps link authority). Indexed
// pages get generous snippet/preview limits so Google can show rich results
// (long snippets, large image previews, full video previews).
...meta?.noindex ? {
robots: {
follow: true,
index: false
}
} : {
robots: {
follow: true,
index: true,
'max-image-preview': 'large',
'max-snippet': -1,
'max-video-preview': -1
}
}
};
}
//# sourceMappingURL=buildMetadata.js.map