UNPKG

react-seo-meta-tags

Version:

SEO metatags for React apps, especially blogs built with Gatsby or NextJS

267 lines (266 loc) 9.78 kB
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 };