SEO meta tags in React/Typescript

Drop-in component to render all SEO meta tags for common websites while using Helmet and React Router

Stop googling markup fragments and validating results and use this component instead. It is optimized for typical usecases and avoids some pitfalls I see over and over again.

Can be seen in action on my webapps, like on eSportsNews.direct.

import React from 'react';
import { Helmet } from 'react-helmet';
import { useHistory } from 'react-router-dom';

// change these constants with your own values
const rootDomain = 'https://esportsnews.direct'
const fallbackImage = rootDomain + '/logo.jpg'

export interface IProps {
  /// should optimally have length <= 60 characters
  title: string

  /// should optimally have 150 <= length <= 160 characters
  description: string

  /// can be relative or absolute or will use a fallback image if not set
  imageUrl?: string

  /// enable this flag to switch from type 'website' to 'article' semantically
  isArticle?: boolean

  /// optional configuration flag to prevent content modifications
  withoutBranding?: boolean
}

/// Drop-in SEO handling of metadata for your page. Does handle vanilla meta tags,
/// opengraph tags for facebook/linkedin/... and twitter cards tags for
/// twitter/slack/... .
export const MetaTags = (props: IProps) => {
  return <>
    <TitleMetaTags {...props} />
    <DescriptionMetaTags {...props} />
    <LinkMetaTags {...props} />
    <ImageMetaTags {...props} />
    <SupplementalMetaTags {...props} />
  </>
}

// apply some branding addendum automatically if not deactivated
const TitleMetaTags = ({ title, withoutBranding }: IProps) => {
  const finalTitle = withoutBranding ? title : title + ' | ' + new URL(rootDomain).hostname
  return <Helmet>
    <title>{finalTitle}</title>
    <meta property="og:title" content={title} />
    <meta property="twitter:title" content={title} />
  </Helmet>
}

// use the given description as-is
const DescriptionMetaTags = ({ description }: IProps) => {
  return <Helmet>
    <meta name="description" content={description} />
    <meta property="og:description" content={description} />
    <meta property="twitter:description" content={description} />
  </Helmet>
}

// normalize the URL to this page generically to generate a canoncial link
const LinkMetaTags = (_props: IProps) => {
  const history = useHistory()
  const link = rootDomain + history.location.pathname
  return <Helmet>
    <link rel="canonical" href={link} />
    <meta property="og:url" content={link} />
  </Helmet>
}

// ensure that there always is an image for social snippets with an absolute URL
const ImageMetaTags = ({ imageUrl }: IProps) => {
  let img = fallbackImage
  if (imageUrl) {
    img = imageUrl.startsWith('/') ? rootDomain + imageUrl : imageUrl
  }
  return <Helmet>
    <meta property="og:image" content={img} />
    <meta property="twitter:image" content={img} />
  </Helmet>
}

// misc data tags
const SupplementalMetaTags = ({ isArticle, imageUrl }: IProps) => {
  const ogType = isArticle ? 'article' : 'website'
  const twitterType = (isArticle && imageUrl) ? 'summary_large_image' : 'summary'
  return <Helmet>
    <meta property="og:type" content={ogType} />
    <meta property="twitter:type" content={twitterType} />
  </Helmet>
}

Technologies: