85 lines
3.0 KiB
TypeScript
85 lines
3.0 KiB
TypeScript
import React, { useEffect } from 'react';
|
|
|
|
// TODO: Update SITE_URL to your actual domain before deploying
|
|
export const SITE_URL = 'https://knuthceramics.com';
|
|
export const SITE_NAME = 'KNUTH Ceramics';
|
|
const DEFAULT_OG_IMAGE = `${SITE_URL}/landingpage/artelier.png`;
|
|
|
|
type Schema = Record<string, unknown>;
|
|
|
|
interface SEOProps {
|
|
title: string;
|
|
description: string;
|
|
canonical?: string;
|
|
schema?: Schema | Schema[];
|
|
ogImage?: string;
|
|
ogType?: 'website' | 'article' | 'product';
|
|
}
|
|
|
|
function setMeta(selector: string, attr: string, value: string) {
|
|
let el = document.querySelector<HTMLMetaElement>(selector);
|
|
if (!el) {
|
|
el = document.createElement('meta');
|
|
const [attrName, attrVal] = selector.replace('meta[', '').replace(']', '').split('="');
|
|
el.setAttribute(attrName, attrVal.replace('"', ''));
|
|
document.head.appendChild(el);
|
|
}
|
|
el.setAttribute(attr, value);
|
|
}
|
|
|
|
const SEO: React.FC<SEOProps> = ({
|
|
title,
|
|
description,
|
|
canonical,
|
|
schema,
|
|
ogImage = DEFAULT_OG_IMAGE,
|
|
ogType = 'website',
|
|
}) => {
|
|
const schemaStr = schema ? JSON.stringify(schema) : null;
|
|
|
|
useEffect(() => {
|
|
const fullTitle = title.includes(SITE_NAME) ? title : `${title} | ${SITE_NAME}`;
|
|
document.title = fullTitle;
|
|
|
|
setMeta('meta[name="description"]', 'content', description);
|
|
setMeta('meta[property="og:title"]', 'content', fullTitle);
|
|
setMeta('meta[property="og:description"]', 'content', description);
|
|
setMeta('meta[property="og:type"]', 'content', ogType);
|
|
setMeta('meta[property="og:image"]', 'content', ogImage);
|
|
setMeta('meta[property="og:site_name"]', 'content', SITE_NAME);
|
|
setMeta('meta[name="twitter:card"]', 'content', 'summary_large_image');
|
|
setMeta('meta[name="twitter:title"]', 'content', fullTitle);
|
|
setMeta('meta[name="twitter:description"]', 'content', description);
|
|
setMeta('meta[name="twitter:image"]', 'content', ogImage);
|
|
|
|
let canonicalEl = document.querySelector<HTMLLinkElement>('link[rel="canonical"]');
|
|
if (!canonicalEl) {
|
|
canonicalEl = document.createElement('link');
|
|
canonicalEl.setAttribute('rel', 'canonical');
|
|
document.head.appendChild(canonicalEl);
|
|
}
|
|
canonicalEl.setAttribute('href', canonical ?? `${SITE_URL}${window.location.pathname}`);
|
|
|
|
// Remove previous page-level schemas, inject new ones
|
|
document.querySelectorAll('script[data-seo="page"]').forEach(el => el.remove());
|
|
if (schemaStr) {
|
|
const schemas = Array.isArray(JSON.parse(schemaStr)) ? JSON.parse(schemaStr) : [JSON.parse(schemaStr)];
|
|
schemas.forEach((s: Schema) => {
|
|
const script = document.createElement('script');
|
|
script.setAttribute('type', 'application/ld+json');
|
|
script.setAttribute('data-seo', 'page');
|
|
script.textContent = JSON.stringify(s);
|
|
document.head.appendChild(script);
|
|
});
|
|
}
|
|
|
|
return () => {
|
|
document.querySelectorAll('script[data-seo="page"]').forEach(el => el.remove());
|
|
};
|
|
}, [title, description, canonical, ogImage, ogType, schemaStr]);
|
|
|
|
return null;
|
|
};
|
|
|
|
export default SEO;
|