Files
codey.lol/src/components/BaseHead.astro

131 lines
5.6 KiB
Plaintext
Raw Normal View History

2025-06-18 07:46:59 -04:00
---
interface Props {
title?: string;
description?: string;
image?: string;
isWhitelabel?: boolean;
2025-12-02 10:05:43 -05:00
whitelabel?: any;
subsite?: any;
2025-06-18 07:46:59 -04:00
}
import { metaData } from "../config";
import { SEO } from "astro-seo";
import { JoyUIRootIsland } from "./Components"
import { useHtmlThemeAttr } from "../hooks/useHtmlThemeAttr";
import { usePrimeReactThemeSwitcher } from "../hooks/usePrimeReactThemeSwitcher";
2025-12-02 10:05:43 -05:00
const { title, description, image, isWhitelabel, whitelabel, subsite } = Astro.props;
2025-06-18 07:46:59 -04:00
const { url } = Astro;
const trimmedTitle = title?.trim();
const seoTitle = trimmedTitle || metaData.title;
2025-12-02 10:05:43 -05:00
// If a whitelabel/subsite override exists, prefer its shareTitle/shareDescription/ogImage/favicon
const subsiteMeta = whitelabel ?? {};
const shareTitle = isWhitelabel
? (trimmedTitle || (subsiteMeta.shareTitle ?? metaData.shareTitle ?? metaData.title))
: (trimmedTitle ? `${trimmedTitle} | ${metaData.title}` : (metaData.shareTitle ?? metaData.title));
const seoTitleTemplate = isWhitelabel ? "%s" : (trimmedTitle ? `%s | ${metaData.title}` : "%s");
2025-12-02 10:05:43 -05:00
const shareDescription = isWhitelabel
? (trimmedTitle || (subsiteMeta.shareDescription ?? metaData.shareDescription ?? metaData.description))
: (description ?? metaData.shareDescription ?? metaData.description);
// Compute canonical URL with these priorities:
// 1. If the whitelabel/subsite provides a baseUrl, use that as the host for the canonical URL
// 2. If a subsite was detected and the request host matches the subsite host, canonical uses that host
// 3. If the request is for a path-based subsite (host is main site), prefer metaData.baseUrl so canonical remains on main site
// 4. Fallback to the request URL or metaData.baseUrl
const currentHost = (Astro.request?.headers?.get('host') || '').split(':')[0];
let canonicalBase: string | null = null;
if (subsiteMeta?.baseUrl) {
canonicalBase = subsiteMeta.baseUrl;
} else if (subsite?.host) {
// normalize hosts for comparison
const requestedHost = (currentHost || '').toLowerCase();
const subsiteHost = String(subsite.host || '').toLowerCase();
if (requestedHost && requestedHost === subsiteHost) {
canonicalBase = `https://${subsite.host}`;
} else {
// keep canonical on the main configured base (path-based subsites should remain under metaData.baseUrl)
canonicalBase = metaData.baseUrl;
}
}
// Decide whether canonical should be the site-root (e.g. https://req.boatson.boats/)
// or include a path. Rules:
// - If canonicalBase comes from a whitelabel/baseUrl or request host matches subsite host -> prefer site-root.
// - Otherwise (path-based subsites), include the pathname/search so canonical remains under the main site path.
const isHostMatchedSubsite = Boolean(subsite?.host && currentHost && currentHost.toLowerCase() === String(subsite.host).toLowerCase());
const isSubsiteRootCanonical = Boolean(subsiteMeta?.baseUrl || isHostMatchedSubsite);
let canonicalUrl: string;
if (canonicalBase) {
if (isSubsiteRootCanonical) {
// ensure canonicalBase ends with a single '/'
canonicalUrl = canonicalBase.endsWith('/') ? canonicalBase : `${canonicalBase}/`;
} else {
canonicalUrl = new URL((url?.pathname ?? '') + (url?.search ?? ''), canonicalBase).toString();
}
} else {
canonicalUrl = url?.href ?? metaData.baseUrl;
}
// Prefer the whitelabel/subsite ogImage when this page is for a whitelabel site.
// Otherwise fall back to an explicit image prop or the global ogImage.
const resolvedOgImage = (isWhitelabel && subsiteMeta.ogImage) ? subsiteMeta.ogImage : (image ?? metaData.ogImage);
// Keep relative/site-root paths as-is (e.g. '/images/req.png') and don't force
// an absolute URL using metaData.baseUrl. Only keep absolute (http/https)
// URLs if the value is already absolute.
function keepRelativeOrAbsolute(val) {
if (!val) return val;
if (/^https?:\/\//i.test(val)) return val; // already absolute
// Normalize to site-root-relative so local testing always resolves under '/'
return val.startsWith('/') ? val : `/${val}`;
}
const shareImage = keepRelativeOrAbsolute(resolvedOgImage);
const shareImageAlt = subsiteMeta.shareImageAlt ?? metaData.shareImageAlt ?? metaData.shareTitle ?? metaData.title;
// Build icon links: prefer subsite icons when present. If a subsite provides a single
// `favicon` but no `icons` array, do NOT append the global icons to avoid duplicates.
const primaryIconHref = keepRelativeOrAbsolute(subsiteMeta.favicon ?? metaData.favicon);
// Ensure extraIcons are used only when subsite doesn't provide a single favicon (prevents duplicates)
const extraIcons = subsiteMeta.icons ? subsiteMeta.icons : (subsiteMeta.favicon ? [] : (metaData.icons ?? []));
2025-06-18 07:46:59 -04:00
---
<SEO
title={seoTitle}
titleTemplate={seoTitleTemplate}
2025-06-18 07:46:59 -04:00
titleDefault={metaData.title}
2025-12-02 10:05:43 -05:00
canonical={canonicalUrl}
description={shareDescription}
2025-06-18 07:46:59 -04:00
charset="UTF-8"
openGraph={{
basic: {
title: shareTitle,
2025-06-18 07:46:59 -04:00
type: "website",
image: shareImage,
url: canonicalUrl,
2025-06-18 07:46:59 -04:00
},
optional: {
description: shareDescription,
2025-12-02 10:05:43 -05:00
siteName: subsiteMeta.siteName ?? metaData.name,
2025-06-18 07:46:59 -04:00
locale: "en_US",
},
}}
2025-12-02 10:05:43 -05:00
extend={{
link: [
2025-12-02 10:05:43 -05:00
// choose subsite favicon if provided, else global config. Allow absolute or relative paths
{ rel: 'icon', href: primaryIconHref },
// additional icon links from config if present
...extraIcons,
],
meta: [
{ property: "og:image:alt", content: shareImageAlt },
{ name: "twitter:card", content: "summary_large_image" },
{ name: "twitter:title", content: shareTitle },
{ name: "twitter:description", content: shareDescription },
{ name: "twitter:image", content: shareImage },
{ name: "twitter:image:alt", content: shareImageAlt },
],
2025-06-18 07:46:59 -04:00
}}
/>