metaforge-seo
Version:
Lightweight SEO metadata manager for React + Vite apps.
136 lines • 3.25 kB
JavaScript
import React, { useEffect } from 'react';
export const SEO = props => {
const {
title,
description,
keywords,
canonicalUrl,
lang = 'en',
ogTitle,
ogDescription,
ogImage,
ogUrl,
twitterCard = 'summary_large_image',
twitterSite,
twitterCreator,
noindex,
nofollow,
jsonLd
} = props;
useEffect(() => {
document.documentElement.lang = lang;
const tags = [title && {
tag: 'title',
props: {
innerText: title
}
}, description && {
tag: 'meta',
props: {
name: 'description',
content: description
}
}, keywords && {
tag: 'meta',
props: {
name: 'keywords',
content: keywords
}
}, canonicalUrl && {
tag: 'link',
props: {
rel: 'canonical',
href: canonicalUrl
}
}, ogTitle && {
tag: 'meta',
props: {
property: 'og:title',
content: ogTitle || title || ''
}
}, ogDescription && {
tag: 'meta',
props: {
property: 'og:description',
content: ogDescription || description || ''
}
}, ogImage && {
tag: 'meta',
props: {
property: 'og:image',
content: ogImage
}
}, ogUrl && {
tag: 'meta',
props: {
property: 'og:url',
content: ogUrl
}
}, twitterCard && {
tag: 'meta',
props: {
name: 'twitter:card',
content: twitterCard
}
}, twitterSite && {
tag: 'meta',
props: {
name: 'twitter:site',
content: twitterSite
}
}, twitterCreator && {
tag: 'meta',
props: {
name: 'twitter:creator',
content: twitterCreator
}
}, (noindex || nofollow) && {
tag: 'meta',
props: {
name: 'robots',
content: `${noindex ? 'noindex' : 'index'}, ${nofollow ? 'nofollow' : 'follow'}`
}
}].filter(Boolean);
tags.forEach(({
tag,
props
}) => {
const el = document.createElement(tag);
Object.entries(props).forEach(([key, value]) => {
if (key === 'innerText') {
el.innerText = value;
} else {
el.setAttribute(key, value);
}
});
document.head.appendChild(el);
});
if (jsonLd) {
const script = document.createElement('script');
script.type = 'application/ld+json';
script.innerText = JSON.stringify({
'@context': jsonLd.context,
'@type': jsonLd.type,
...jsonLd.data
});
document.head.appendChild(script);
}
return () => {
tags.forEach(({
tag,
props
}) => {
const selector = props.name ? `${tag}[name='${props.name}']` : props.property ? `${tag}[property='${props.property}']` : props.rel ? `${tag}[rel='${props.rel}']` : tag;
const elements = document.head.querySelectorAll(selector);
elements.forEach(el => el.remove());
});
if (jsonLd) {
const scripts = document.head.querySelectorAll("script[type='application/ld+json']");
scripts.forEach(el => el.remove());
}
};
}, [props]);
return null;
};
SEO.displayName = 'SEO';
export default SEO;