Blog » How to improve SEO and social media preview of your website with React Helmet and Prerender service

How to improve SEO and social media preview of your website with React Helmet and Prerender service

2018-10 |

Ta treść jest dostępna tylko w języku angielskim

How to improve SEO and social media preview of your website with React Helmet and Prerender service

Hard to imagine, but it's over 1 year since I created this blog, and up until recently, it always had static head tags. Title always being the same, for example, wasn't that much of an issue to me, but social meta tags never related to the content of the post I'm sharing on Twitter, that was not cool (it's also not cool when it comes to SEO, but it's not that much of my concern right now). I finally had to tackle it. Here's a simple way to do it that I've found.

My solution is based on React Helmet library. It provides Helmet higher order component, which is used to wrap whatever head tags one wants to put on his website. It supports all valid head tags, so it can be used to add JS scripts and CSS files to the page as well, but for me, title and meta tags was all I needed.

In my implementation, I created Meta component as a decorator over React Helmet, so that I can switch it to a different library easily, whenever I need to. Meta component is also responsible for translating a few information about given view into title, description, keywords, and bunch of valid social media tags supporting Facebook and Twitter.

import * as React from 'react';
import { Helmet } from 'react-helmet';

export interface MetaPropsInterface {
  title?: string;
  subtitle?: string;
  description?: string;
  keywords?: string;
  imageUrl?: string;
}

export const Meta = (props: MetaPropsInterface) => {
  const { title, subtitle, description, keywords, imageUrl } = props;

  const metaElements = [];

  if (title) {
    metaElements.push(
      <title key="title">{title}</title>,
      <meta key="og:site_name" property="og:site_name" content="{title}"/>,
      <meta key="twitter:site" property="twitter:site" content="{title}"/>,
      subtitle
        ? <meta key="twitter:title" property="twitter:title" content="{subtitle}"/>
        : <meta key="twitter:title" property="twitter:title" content="{title}"/>,
    );
  }

  if (description) {
    metaElements.push(
      <meta key="description" name="description" content="{description}"/>,
      <meta key="og:description" property="og:description" content="{description}"/>,
      <meta key="twitter:description" property="twitter:description" content="{description}"/>,
    );
  }

  if (keywords) {
    metaElements.push(
      <meta key="keywords" name="keywords" content="{keywords}"/>,
    );
  }

  if (imageUrl) {
    metaElements.push(
      <meta key="og:image" name="og:image" content="{imageUrl}"/>,
      <meta key="twitter:image" name="twitter:image" content="{imageUrl}"/>,
    );
  }

  return(
    <Helmet>
      {metaElements}
    </Helmet>
  );
};

export default Meta;

Probably the coolest thing about this library is that nested components override duplicate changes. Therefore, I don't have to take care of removing any tags from head provided by parent view, for example homepage, when user navigates to child view, such as entry - they are overwritten automatically. That's why I could straight up use my Meta component here and there (example below comes from entry view), and my job was done.

import * as React from 'react';
import { inject, observer } from 'mobx-react';

import AssetsProvider from 'common/AssetsProvider';
import LabelsProvider from 'common/LabelsProvider';
import { LanguageStoreInterface } from 'store/language';

import Meta from 'components/Meta';

interface EntryMetaPropsInterface {
  title: string;
  description: string;
  keywords: string[];
  imageFileName: string;
  languageStore?: LanguageStoreInterface;
}

export const EntryMeta = (props: EntryMetaPropsInterface) => {
  const { title, description, keywords, imageFileName, languageStore: { getLanguage } } = props;
  return <Meta title="{LabelsProvider.getLabel('page_title__entry'," {="" entry_title:="" },="" getLanguage())}="" description="{LabelsProvider.getLabel('page_description'," keywords="{LabelsProvider.getLabel('page_keywords'," keywords:="" keywords.join(',="" ')="" imageUrl="{AssetsProvider.getEntryImageFilePath(imageFileName)}"/>
};

export default inject('languageStore')(observer(EntryMeta));

That's it! Dynamic head tags are now supported.

But it doesn't work. When I check link to any entry from my blog with Facebook Sharing Debugger or Twitter Card Validator, it appears as it was homepage - the same, static title and other meta data is presented. However, when I access the same address from my browser, I see them modified accordingly. What's wrong?

Facebook and Twitter, as well as Google and other major search engines, typically don't run JavaScript. As it happens, my blog is written almost entirely in JavaScript (well, to be precise, it's in TypeScript, but for browser, it's compiled to JS). If you want to see it how those bots see it, turn off JavaScript in your browser and refresh the page (but not before you finish reading!). It's pretty much empty website with static, general title and meta tags. That's how it appears to Facebook.

There are plenty of ways to solve it, one of them being rewriting this blog to some other, server-rendered language. Luckily, nowadays, we don't have to do that anymore, as JavaScript may very well be rendered on a server side as well. The technique is called Server Side Rendering (SSR, you might've heard of it ;-)), and it basically renders your application on server as it was rendered by client, caches it, then sends to the client plain, static files. This way, no JavaScript on client side is involved, he receives only HTML and CSS, which can be handled by almost every web browser without effort. Most popular implementations of this idea in JavaScript utilize NextJS framework and NodeJS server.

SSR is really vast topic, and I don't want to mix it in this entry too much. I'm going to discover this area of programming in JS with you in separate, dedicated entries, as it's gaining more and more popularity lately, is getting more and more complex, and is really cool, interesting concept, that I'm getting into more and more every day. Today, however, we'll investigate much simpler way for SSR.

Instead of implementing this whole thing myself, I'll just pay someone to do it (hopefully, but more on this in a second), for there is already number of services providing prerendering out of the box, with little to none effort on your side. An example of such service might be prerender.io, but I've selected different one.

prerender.cloud, my weapon of choice, works similarly to general mechanism I've described: they take my website, render and cache it on their side, then provide user with static content, which is later replaced by actual client-side application, whenever it's ready to go (you can find more details on how it works in dedicated section of their documentation). All I had to do is redirect my traffic to their servers conditionally (exemplary .htaccess file can be found here). And of course pay for it, but I had no issue with that yet. It's free for up to 500 requests monthly, so I have still have some room for growth in popularity left.

All in all, after busy day in my workshop, I managed to accomplish my task: my blog is finally social media friendly. And, I must admit, it wasn't that hard, technically speaking. It was mostly a matter of research and learning. I wish you similarly pleasant journey with this kind of assignment!

As usual, you can find all the changes I described in this post on my blog repository on GitHub.

PS It doesn't work for me personally at this moment, since my hosting provider has a hard time enabling some Apache modules. I'm moving to different provider soon, I'll update this post once you can actually test this feature on my blog!

Inne wpisy

How to set up tooling around your open source project

2019-11 |

Ta treść jest dostępna tylko w języku angielskim

How to set up tooling around your open source project

Lately in my project we had very particular need for specific validation of content of JavaScript bundle produced by Webpack. In the process of extensive research we have not found any existing tool to serve this purpose. Therefore, I've created Webpack Bundle Content Validator, which is a Webpack plugin and a CLI tool for Webpack bundle content validation. More importantly, I made it open source. Surprisingly, simply publishing it to NPM wasn't enough - I decided to set up continuous integration, automated unit tests code coverage calculation, automated check for vulnerabilities and a few other things, in order to make it legitimate and reliable package. In this post I'm describing those steps in details.

Czytaj dalej

Webpack 4 config explained (with example)

2018-04 |

Ta treść jest dostępna tylko w języku angielskim

Webpack 4 config explained (with example)

Using a skeleton for your application prepared by someone else comes with great benefit of a lot of time saved, but also with huge cost of a lot of knowledge not obtained. Sometimes you'll manage to complete your assignment just fine with some predefined boilerplate, without too much need for deep investigation of it's nooks and crannies. Other times, you'll end up in a position, where you reverse engineer it in order to introduce some major change, or just give up and start from scratch with your own thing. I wouldn't like you to give up on my application skeleton. Thus, I'll describe some of it's shenanigans in it's documentation. Today, I'm explaining build process.

Czytaj dalej

How to setup routing for Not Found on both sides with React-Router and Express

2018-03 |

Ta treść jest dostępna tylko w języku angielskim

How to setup routing for Not Found on both sides with React-Router and Express

When building web applications in React, I usually choose Express to be my server, and more often than not I use React-Router to manage redirections and changes in history. Not without a reason - both are among the most popular choices in their respective fields nowadays; both are simple and elegant in every day work. However, I had some tough moments with both of them when it came up to setting all unrecognized routing to "Not Found" page, and this piece came as a result of them.

Czytaj dalej

Syntax highlighting in React with highlight.js and Web Worker

2018-09 |

Ta treść jest dostępna tylko w języku angielskim

Syntax highlighting in React with highlight.js and Web Worker

One of the most common issues I heard people have with my blog was lack of syntax highlighting in posts, especially those, which contain a lot of code. Okay, it's almost 2019, I'm a software engineer, working mostly with front-end these times - I finally agreed, that it should be added. So I added it. And in the meantime, I also learned a little bit about Web Workers. Hence, this post, in which I describe this little adventure.

Czytaj dalej

Moving from Sass to Styled Components (with snapshot tests)

2018-08 |

Ta treść jest dostępna tylko w języku angielskim

Moving from Sass to Styled Components (with snapshot tests)

Who doesn't like to constantly rework perfectly fine stuff into something new and fancy just because it's trendy now? Well, probably pretty much every single JS developer, most certainly everyone who works with this language long enough to experience at least a glimpse of famous "JS fatigue" feeling (so approximately a few weeks). However, I created this blog as a playground to try out new libraries and frameworks, and since I'm learning Styled Components for my professional work at the moment, even though Sass-based styling worked perfectly for my needs, I reimplemented all of it into this controversial CSS-in-JS. And I'm still sane!

Czytaj dalej