react-seo-meta-tags
Version:
SEO metatags for React apps, especially blogs built with Gatsby or NextJS
267 lines (266 loc) • 9.78 kB
JavaScript
import * as React from "react";
function generateWebsite({
author,
datePublished,
description,
language,
title,
url,
image,
site
}) {
return {
"@context": "http://schema.org",
"@type": "WebPage",
datePublished,
description,
image,
inLanguage: language || "en-US",
name: title,
url,
author: author && generatePerson(author),
potentialAction: site && site.searchUrl && {
"@type": "SearchAction",
/**
* @example "https://www.google.com/search?q=asdf"
*/
target: `${site.searchUrl}{search_term_string}`,
"query-input": "required name=search_term_string"
}
};
}
function generatePerson({ email, name, image }) {
return {
"@type": "Person",
name,
email,
image
};
}
function generateBreadcrumbList(breadcrumbList) {
return {
"@context": "http://schema.org",
"@type": "BreadcrumbList",
itemListElement: breadcrumbList.map((b, i) => ({
"@type": "ListItem",
position: i,
item: {
"@id": b.url,
"@type": "WebPage",
url: b.url,
name: b.title,
image: b.image
}
}))
};
}
function generateOrganization(organization) {
return {
"@context": "http://schema.org",
"@type": organization["@type"] ? organization["@type"] : "Organization",
description: organization.description,
name: organization.name,
legalName: organization.legalName,
sameAs: organization.sameAs,
url: organization.url,
logo: organization.logo,
parentOrganization: organization.parentOrganization && generateOrganization(organization.parentOrganization)
};
}
function generateBlogPosting({
url,
title,
description,
image,
datePublished,
dateModified,
tags,
author,
publisher
}) {
return {
"@context": "http://schema.org",
"@type": "BlogPosting",
url,
name: title,
/**
* From https://developers.google.com/search/docs/data-types/article#article_types
* "The headline of the article. Headlines should not exceed 110 characters."
*/
headline: title,
keywords: tags,
description,
author: author && generatePerson(author),
publisher: publisher && generateOrganization(publisher),
mainEntityOfPage: {
/**
* From example markup of JSON-LD https://developers.google.com/search/docs/data-types/article
*/
"@type": "WebPage",
/**
* Indicates that this BlogPosting is the main thing in this URL.
*/
"@id": url
},
/**
* From https://developers.google.com/search/docs/data-types/article#article_types
* "Images should be at least 1200 pixels wide."
*/
image,
// thumbnailUrl ?
datePublished,
/**
* Recommended by https://search.google.com/structured-data/testing-tool
*
* Reasoning https://bts.nomadgate.com/medium-evergreen-content ->
* Not only does Google prefer to feature more recent content in its search results, but
* users are also more likely to click an article with a recent date listed next to it.
* Does it make sense as you can just manipulate the date? Eeeh... Perhaps Google is aware of that.
*/
dateModified
};
}
class ReactSEOMetaTags extends React.PureComponent {
/**
* General tags. OG and Twitter tags both fallback on these three incase description or image is not provided.
* However by the way this library works, OG and Twitter tags are both extended from the same objects so
* they'll always be defined or undefined.
* @param props
*/
renderGeneral({ title, description, image }) {
return [
/* @__PURE__ */ React.createElement("title", { key: "title" }, title),
description && /* @__PURE__ */ React.createElement("meta", { key: "description", name: "description", content: description }),
image && /* @__PURE__ */ React.createElement("meta", { key: "image", name: "image", content: image })
];
}
renderNonBlogOgTags() {
return [/* @__PURE__ */ React.createElement("meta", { key: "og:type", property: "og:type", content: "website" })];
}
renderBlogOgTags({ datePublished, dateModified }) {
return [
/* @__PURE__ */ React.createElement("meta", { key: "og:type", property: "og:type", content: "article" }),
datePublished && /* @__PURE__ */ React.createElement(
"meta",
{
key: "article:published_time",
property: "article:published_time",
content: datePublished
}
),
dateModified && /* @__PURE__ */ React.createElement("meta", { key: "article:modified_time", property: "article:modified_time", content: dateModified })
];
}
/**
* https://developers.facebook.com/docs/sharing/webmasters/
* http://ogp.me/
* @param props
*/
renderFacebook(props) {
const {
url,
title,
description,
image,
imageAlt,
video,
audio,
site,
language,
facebookAppId
} = props;
return [
url && /* @__PURE__ */ React.createElement("meta", { key: "og:url", property: "og:url", content: url }),
// Important
/* @__PURE__ */ React.createElement("meta", { key: "og:locale", property: "og:locale", content: language || "en-US" }),
/* @__PURE__ */ React.createElement("meta", { key: "og:title", property: "og:title", content: title }),
// Important
description && /* @__PURE__ */ React.createElement("meta", { key: "og:description", property: "og:description", content: description }),
// Somewhat important
// Facebook recommends 1200x630 size, ratio of 1.91:1 but 1200x1200 is also fine
image && /* @__PURE__ */ React.createElement("meta", { key: "og:image", property: "og:image", content: image }),
// Important
imageAlt && /* @__PURE__ */ React.createElement("meta", { key: "og:image:alt", property: "og:image:alt", content: imageAlt }),
// For visually impaired people
video && /* @__PURE__ */ React.createElement("meta", { key: "og:video", property: "og:video", content: video }),
audio && /* @__PURE__ */ React.createElement("meta", { key: "og:audio", property: "og:audio", content: audio }),
site && site.siteName && /* @__PURE__ */ React.createElement("meta", { key: "og:site_name", property: "og:site_name", content: site.siteName }),
// Eeh... can't hurt?
facebookAppId && /* @__PURE__ */ React.createElement("meta", { key: "fb:app_id", property: "fb:app_id", content: facebookAppId })
];
}
renderTwitter(props) {
const { title, description, image, imageAlt, cardType, twitterUser, twitterSite } = props;
return [
image && /* @__PURE__ */ React.createElement("meta", { key: "twitter:card", name: "twitter:card", content: cardType || "summary_large_image" }),
twitterUser && /* @__PURE__ */ React.createElement("meta", { key: "twitter:creator", name: "twitter:creator", content: twitterUser }),
twitterSite && /* @__PURE__ */ React.createElement("meta", { key: "twitter:site", name: "twitter:site", content: twitterSite }),
/* @__PURE__ */ React.createElement("meta", { key: "twitter:title", name: "twitter:title", content: title }),
description && /* @__PURE__ */ React.createElement("meta", { key: "twitter:description", name: "twitter:description", content: description }),
image && /* @__PURE__ */ React.createElement("meta", { key: "twitter:image", name: "twitter:image", content: image }),
imageAlt && /* @__PURE__ */ React.createElement("meta", { key: "twitter:image:alt", name: "twitter:image:alt", content: imageAlt })
];
}
renderBlogPostSEO(website, blogPost, props) {
const { breadcrumb, facebook, twitter, organization } = props;
return [
this.renderGeneral({ ...website, ...blogPost }),
/* @__PURE__ */ React.createElement("script", { key: "application/ld+json", type: "application/ld+json" }, JSON.stringify(
[
generateWebsite(blogPost),
breadcrumb && generateBreadcrumbList(breadcrumb),
generateBlogPosting(blogPost),
organization && generateOrganization(organization)
].filter((obj) => obj !== void 0 && obj !== null)
)),
this.renderBlogOgTags(blogPost),
this.renderFacebook({ ...website, ...blogPost, ...facebook }),
this.renderTwitter({ ...website, ...blogPost, ...twitter })
];
}
renderWebsiteSEO(website, props) {
const { facebook, breadcrumb, twitter, organization } = props;
return [
this.renderGeneral(website),
/* @__PURE__ */ React.createElement(
"script",
{ key: "application/ld+json", type: "application/ld+json" },
/**
* Stringifying eliminates the undefined values, which keeps the JSON-LD somewhat tidy.
* Some empty objects might remain, but that shouldn't be a problem.
*/
JSON.stringify(
[
generateWebsite(website),
breadcrumb && generateBreadcrumbList(breadcrumb),
organization && generateOrganization(organization)
].filter((obj) => obj !== void 0 && obj !== null)
)
),
this.renderNonBlogOgTags(),
this.renderFacebook({ ...website, ...facebook }),
this.renderTwitter({ ...website, ...twitter })
];
}
render() {
let el;
const { website, blogPost, render, ...rest } = this.props;
if (blogPost) {
el = this.renderBlogPostSEO(website, blogPost, rest);
} else if (website) {
el = this.renderWebsiteSEO(website, rest);
}
if (render) {
return render(el);
}
return el;
}
}
export {
ReactSEOMetaTags,
ReactSEOMetaTags as default,
generateBlogPosting,
generateBreadcrumbList,
generateOrganization,
generateWebsite
};