diff --git a/client/components/SEO.tsx b/client/components/SEO.tsx new file mode 100644 index 00000000..404e8e85 --- /dev/null +++ b/client/components/SEO.tsx @@ -0,0 +1,52 @@ +import { useEffect } from "react"; + +export type SEOProps = { + pageTitle: string; + description?: string; + image?: string | null; + canonical?: string | null; + noIndex?: boolean; +}; + +function upsertMeta(selector: string, attrs: Record) { + let el = document.querySelector(selector) as HTMLElement | null; + if (!el) { + const tag = selector.startsWith('meta[') ? 'meta' : selector.startsWith('link[') ? 'link' : 'meta'; + el = document.createElement(tag); + document.head.appendChild(el); + } + Object.entries(attrs).forEach(([k, v]) => (el as any).setAttribute(k, v)); +} + +export default function SEO({ pageTitle, description, image, canonical, noIndex }: SEOProps) { + useEffect(() => { + const title = `AeThex | ${pageTitle}`; + document.title = title; + + if (canonical) { + upsertMeta('link[rel="canonical"]', { rel: 'canonical', href: canonical }); + upsertMeta('meta[property="og:url"]', { property: 'og:url', content: canonical }); + } + + if (description) { + upsertMeta('meta[name="description"]', { name: 'description', content: description }); + upsertMeta('meta[property="og:description"]', { property: 'og:description', content: description }); + upsertMeta('meta[name="twitter:description"]', { name: 'twitter:description', content: description }); + } + + upsertMeta('meta[property="og:title"]', { property: 'og:title', content: title }); + upsertMeta('meta[name="twitter:title"]', { name: 'twitter:title', content: title }); + + if (image) { + upsertMeta('meta[property="og:image"]', { property: 'og:image', content: image }); + upsertMeta('meta[name="twitter:image"]', { name: 'twitter:image', content: image }); + } + + if (noIndex) { + upsertMeta('meta[name="robots"]', { name: 'robots', content: 'noindex, nofollow' }); + upsertMeta('meta[name="googlebot"]', { name: 'googlebot', content: 'noindex, nofollow' }); + } + }, [pageTitle, description, image, canonical, noIndex]); + + return null; +}