DEV Community

Cover image for Handling SEO in React apps
smakosh
smakosh

Posted on • Originally published at smakosh.com on

Handling SEO in React apps

Originally published on my blog

I have landed successfully in Bali, rented a scooter and been enjoying my ride from my place to Dojo Bali where I’m currently writing this article.

SEO definition

Search engine optimization (SEO) is the process of increasing the quality and quantity of website traffic, increasing visibility of a website or a web page to users of a web search engine.

Taken from Wikipedia

The SPA issues with SEO myth

All React apps built using CRA (create-react-app) or Gatsby to serve dynamic content are single page apps, meaning that there is one index.html file that renders the content of your app, which means that all your meta tags within your components won’t be crawled for indexing on search engines, or it used to be! As of May 2019, Google announced that its crawler can now crawl JavaScript just like the Twitter/Facebook/Slack/Telegram crawlers, so when ever a non technical person mentions this SPA SEO myth, make sure to enlighten them.

What we used to do before crawlers turned out to be able to crawl JavaScript

Most React developers used Next Js as it offers server side rendering, meaning that the pages are generated on demand when the user visits a certain url or use a technique called Pre-rendering which is basically server side rendering during build time, Gatsby still uses this approach to generate the pages for your static content but doesn’t offer that feature for dynamic content unlike some services such as Netlify that offers pre-rendering for your dynamic content.

SEO component

Inspired from this GitHub repository which contains all you need to include in your header tag, I made this SEO component that I re-use almost on all my Gatsby/React apps to handle SEO

I added few comments within the code explaining each part

import React from 'react'
import Helmet from 'react-helmet'

// This is the thumbnail that appears when someone shares your website
import Thumbnail from 'assets/me.jpg'
import {
  url,
  defaultTitle,
  defaultDescription,
  social,
  socialLinks,
  address,
  contact,
  legalName,
  foundingDate,
  logo,
  author,
} from 'data/config'

export const SEO = ({
  title,
  type,
  description,
  articleBody,
  datePublished,
  dateModified,
  cover,
  location = '',
  readTime,
}) => {

// This is Structured data that is recommended to have according to Google
  // You can read more about it on Google's own documentation about structured data
  // The first string is for the Article schema and the second one for the organization schema
  const structuredDataArticle = `{
        "@context": "http://schema.org",
        "@type": "${type}",
        "mainEntityOfPage": {
            "@type": "WebPage",
            "@id": "https://google.com/article"
        },
        "headline": "${description}",
        "image": "${
      cover ? `https://smakosh.com${cover}` : `https://smakosh.com${Thumbnail}`
    }",
        "datePublished": "${datePublished}",
        "dateModified": "${dateModified}",
        "author": {
            "@type": "Person",
            "name": "${author}"
        },
        "articleBody": "${articleBody}",
        "publisher": {
            "@type": "Organization",
            "name": "${author}",
            "logo": {
                "@type": "ImageObject",
                "url": "${logo}"
            }
        },
        "description": "${description}",
        "url": "${url}${location}/?ref=smakosh.com"
  }`

  const structuredDataOrganization = `{
        "@context": "http://schema.org",
        "@type": "${type}",
        "legalName": "${legalName}",
        "url": "${url}",
        "logo": "${logo}",
        "foundingDate": "${foundingDate}",
        "founders": [{
            "@type": "Person",
            "name": "${legalName}"
        }],
        "contactPoint": [{
            "@type": "ContactPoint",
            "email": "${contact.email}",
            "telephone": "${contact.phone}",
            "contactType": "customer service"
        }],
        "address": {
            "@type": "PostalAddress",
            "addressLocality": "${address.city}",
            "addressRegion": "${address.region}",
            "addressCountry": "${address.country}",
            "postalCode": "${address.zipCode}"
        },
        "sameAs": [
            "${socialLinks.twitter}",
            "${socialLinks.google}",
            "${socialLinks.youtube}",
            "${socialLinks.linkedin}",
            "${socialLinks.instagram}",
            "${socialLinks.github}"
        ]
    }`

  return (
    // Notice I'm using react-helmet to inject these elements within the header tag
    <Helmet>
      {/* The description that appears under the title of your website appears on search engines results */}
      <meta name="description" content={description || defaultDescription} />

      {/* The thumbnail of your website */}
      <meta
        name="image"
        content={cover ? `${url}${cover}` : `${url}${Thumbnail}`}
      />

      {/* Opengraph meta tags for Facebook & LinkedIn */}
      <meta property="og:url" content={`${url}${location}/?ref=smakosh.com`} />
      <meta
        property="og:type"
        content={type === 'NewsArticle' ? 'NewsArticle' : 'website'}
      />
      <meta
        property="og:title"
        content={title ? `Smakosh | ${title}` : defaultTitle}
      />
      <meta
        property="og:description"
        content={description || defaultDescription}
      />
      <meta
        property="og:image"
        content={cover ? `${url}${cover}` : `${url}${Thumbnail}`}
      />

      {/* You can get this id when you create an app id on Facebook of your Facebook page */}
      <meta property="fb:app_id" content={social.facebook} />

      {/* These tags work for Twitter & Slack, notice I've included more custom tags like reading time etc... */}
      <meta name="twitter:card" content="summary" />
      <meta name="twitter:creator" content={socialLinks.twitter} />
      <meta name="twitter:site" content={social.twitter} />
      <meta
        name="twitter:title"
        content={title ? `Smakosh | ${title}` : defaultTitle}
      />
      <meta
        name="twitter:description"
        content={description || defaultDescription}
      />
      <meta
        name="twitter:image:src"
        content={cover ? `${url}${cover}` : `${url}${Thumbnail}`}
      />
      {type === 'NewsArticle' && (
        <meta name="twitter:label1" value="Reading time" />
      )}
      {type === 'NewsArticle' && (
        <meta name="twitter:data1" value={`${readTime} min read`} />
      )}
      {type === 'NewsArticle' && (
        <meta name="author" content="Ismail Ghallou" data-react-helmet="true" />
      )}
      {type === 'NewsArticle' && (
        <meta
          name="article:published_time"
          content={datePublished}
          data-react-helmet="true"
        />
      )}

      {/* Structured data */}
      <script type="application/ld+json">
        {type === 'NewsArticle'
          ? structuredDataArticle
          : structuredDataOrganization}
      </script>

      {/* Not sure if this is still relevant as Google shut down their Google+ paltform */}
      <link rel="publisher" href={socialLinks.google} />

      {/* The title of your current page */}
      <title>{title ? `Smakosh | ${title}` : defaultTitle}</title>

      {/* Default language and direction */}
      <html lang="en" dir="ltr" />
    </Helmet>
  )
}
Enter fullscreen mode Exit fullscreen mode

And I include it on each component that is meant to act like a page like so

import React from 'react'
import SEO from './SEO'

export default () => (
  <div>
    <SEO title="Home page" location="/" type="Organization" />
    <h1>Home page</h1>
  </div>
)
Enter fullscreen mode Exit fullscreen mode

You can get this component from the code source of this blog.

Tips & tools to rank your website higher on search results

If your website is a blog like this website, it’s highly recommend to have a RSS feed like this one as some apps and extension do crawl the feed and will bring you more visitors that will boost your ranking on search results.

Having a sitemap helps as well and you have to submit it to your Google Search console.

You can test your structured data here

Feel free to drop your questions down in the comments section and I’ll do my best to answer them all.

Top comments (6)

Collapse
 
jcmarquet profile image
Jean-Christophe MARQUET

Thank you for this great read.
"so when ever a non technical person mentions this SPA SEO myth, make sure to enlighten them" => I'm pretty sure even amongst the technical people there are those who don't know that. It's perfectly ok since the change occurred recently but it makes your sharing even more interesting.

Collapse
 
smakosh profile image
smakosh

Yup it's still 1 month old

Collapse
 
smart_egg profile image
Dmitriy A.

It's always worth trying to use pre-rendering service and compare its results before and after using tools like Google Rich Results and Google structured data testing tool.

From my experience pre-rendered pages are above and beyond compared to rendered with SSR, or fetched by Google crawler with enabled JS.

Collapse
 
guillaumepn profile image
guillaumepn

Thanks for the article. But my main issue with helmet is that it wouldn't display preview data in links shared on social networks. Do you know if your method solves that?

Collapse
 
smakosh profile image
smakosh

Are you sure you have read the article?

Collapse
 
smakosh profile image
smakosh