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 |

This content is only available in english

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!

Other blog posts

Back in the day, my JS projects were small and self-contained. Nowadays, in my professional work, majority, if not all of front-end applications I am working on are connected to multiple back-end services for variety of reasons. It gives me a freedom of not caring of the back-end, as long as we have defined a contract, and proceeding happily with what actually matters, i.e. colors and animations. But it also gave ma some headache, when I wanted to have pleasant, convenient coding environment on my localhost, and not have all of these weird back-end stuff running on my local machine as well. Here's how I worked it out with both Webpack Dev Server and Express.

Continue reading

How to set up tooling around your open source project

2019-11 |

This content is only available in english

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.

Continue reading

Webpack 4 config explained (with example)

2018-04 |

This content is only available in english

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.

Continue reading

How to integrate Disqus comments in React app

2017-09 |

This content is only available in english

How to integrate Disqus comments in React app

One of the challenges that I faced while programming this, so far, very simple blog app (the one that you are using to read this post, most likely), was how to give my readers a possibility to comment on my posts. Obviously, it is one of the crucial features of this kind of app – I would like my readers to be able to tell me where am I wrong, and I would like me to be able to respond to such heretic claims. However, the whole feature seems quite a lot of work to implement from scratch. Luckily, there are plenty of ready out of the box solutions out there, one of them being Disqus.

Continue reading

My first MobX store

2018-07 |

This content is only available in english

My first MobX store

My dad wants to read my blog. The only issue is that he doesn't speak English very well. It's communicative, but it's not quite enough to understand intricate, sophisticated Shakespearean language, I am decorating my posts with. Worry not, father, as I've found the solution: language versions. I am currently working on adaptations of my posts in Polish language. In the meantime, I'm also adapting my codebase to be able to recognize and properly handle language parameter. And for that purpose, for the first time, I decided to use MobX.

Continue reading