File: //tmp/dateweb-usePageMeta.ts
import { useEffect } from 'react';
type PageMetaOptions = {
canonical?: string;
ogTitle?: string;
ogDescription?: string;
ogImage?: string;
twitterImage?: string;
jsonLd?: Record<string, unknown>;
noIndex?: boolean;
};
function ensureMeta(selector: string, attribute: string, value: string) {
const tag = document.querySelector(selector) || document.createElement('meta');
tag.setAttribute(attribute, value);
if (!tag.parentElement) {
document.head.appendChild(tag);
}
}
function ensureLink(rel: string, href: string) {
const tag = document.querySelector('link[rel="' + rel + '"]') || document.createElement('link');
tag.setAttribute('rel', rel);
tag.setAttribute('href', href);
if (!tag.parentElement) {
document.head.appendChild(tag);
}
}
export function usePageMeta(title: string, description: string, options: PageMetaOptions = {}) {
useEffect(() => {
document.title = title;
ensureMeta('meta[name="description"]', 'content', description);
const canonicalUrl = options.canonical || (window.location.origin + window.location.pathname);
ensureLink('canonical', canonicalUrl);
const jsonLdPayload = options.jsonLd ? JSON.stringify(options.jsonLd) : '';
const ogTitle = options.ogTitle || title;
const ogDescription = options.ogDescription || description;
const ogImage = options.ogImage || '/media/generated/story-terrace-evening.webp';
const twitterImage = options.twitterImage || ogImage;
ensureMeta('meta[property="og:title"]', 'content', ogTitle);
ensureMeta('meta[property="og:description"]', 'content', ogDescription);
ensureMeta('meta[property="og:type"]', 'content', 'website');
ensureMeta('meta[property="og:image"]', 'content', ogImage);
ensureMeta('meta[property="og:url"]', 'content', canonicalUrl);
ensureMeta('meta[name="twitter:card"]', 'content', 'summary_large_image');
ensureMeta('meta[name="twitter:title"]', 'content', ogTitle);
ensureMeta('meta[name="twitter:description"]', 'content', ogDescription);
ensureMeta('meta[name="twitter:image"]', 'content', twitterImage);
const robotsTag = document.querySelector('meta[name="robots"]') || document.createElement('meta');
robotsTag.setAttribute('name', 'robots');
robotsTag.setAttribute('content', options.noIndex ? 'noindex, nofollow' : 'index, follow');
if (!robotsTag.parentElement) {
document.head.appendChild(robotsTag);
}
const existingJsonLd = document.querySelector('script[data-dateweb-json-ld="1"]');
if (jsonLdPayload) {
if (existingJsonLd) {
existingJsonLd.textContent = jsonLdPayload;
} else {
const jsonLdTag = document.createElement('script');
jsonLdTag.type = 'application/ld+json';
jsonLdTag.dataset.datewebJsonLd = '1';
jsonLdTag.textContent = jsonLdPayload;
document.head.appendChild(jsonLdTag);
}
} else if (existingJsonLd) {
existingJsonLd.remove();
}
}, [
title,
description,
options.canonical,
options.ogTitle,
options.ogDescription,
options.ogImage,
options.twitterImage,
options.noIndex,
options.jsonLd ? JSON.stringify(options.jsonLd) : '',
]);
}