React App into Static HTML with Meta Tags

24-01-2019




Imgur



In this post, I'll show you how to pre-render a normal React app (created with create-react-app) into static HTML, and I'll also show you how to add meta tags to your page to help with SEO and SMO.



React-Snap

React-Snap is a tool use to "pre-renders a web app into static HTML. Uses Headless Chrome to crawl all available links starting from the root." We'll use it to pre-renders our React app into static HTMLs and serve those HTMLs via Express.js.

Advantages of serving static HTML files is that it works better with SEO and SMO.


SEO (Search Engine Optimisation)

Google search bots can more easily crawls through static HTML pages. While, the bot can also crawls through client side javascript rendered web site, it might take a longer time, and might not work as expected if the web side javascript app doesn't build properly.


SMO (Social Media Optimisation)

Statc HTMLs with the help of react-helmet allow the web site page to be share with images and page descriptions to social media site.


Installations

In the react project root directory, install the react-snap dev dependency.

Using npm:

0
$ npm install --dev react-snap

Using yarn:

0
$ yarn add --dev react-snap

Usage

Then add a "postbuild" script to to your react package.json's scripts property:

package.json

0
1
2
"scripts": {
  "postbuild": "react-snap"
}

And in your src/index.js (for React.js 16+) change to the following:

index.js

0
1
2
3
4
5
6
7
import { hydrate, render } from "react-dom";

const rootElement = document.getElementById("root");
if (rootElement.hasChildNodes()) {
  hydrate(<App />, rootElement);
} else {
  render(<App />, rootElement);
}

To build and pre-render the app into static HTMLs run in your react project root directory. The postbuild script will run automatically after the "build" script.

0
$  npm run build

This will create static HTML files inside the build/ directory. With react-snap, 200.html will be use as the new index.html.


Problem with react-router

While pre-rendering my own website using react-snap, I've found that the Link component of react-router doesn't work as expected. react-snap uses Headless Chrome to crawl through each route of the React app. In my website, I've use the Link component to pass a state to the routed page:

0
1
2
3
4
5
6
7
8
9
render() {
  return (
    <Link to={{ 
      pathname: '/posts/generate_static_site',
      state: { state0: "0", state1: "1" }
    }}>
      ...
    </Link>
  )
}

Headless Chrome will render the passed state as undefined. Therefore, I had to use other ways to get additional data from the Link route. What I've come up with is to put the data in a separate file instead and get it using the Link url parameters:

Data.js

0
1
2
3
4
5
6
7
8
9
10
11
export default (id) => {
  return data[id];
};

const data =  {
  "id0": {
    data: "Hello there"
  },
  "id1": {
    data: "General Kenobi"
  }
};

RoutedPage.jsx

0
1
2
3
componentWillMount() {
    const { postname } = this.props.match.params;
    const { data } = GetData(id);
}

You could also store your data inside a database, and query it via an API request to your backend RESTful server.



React-Helmet

React-Helmet output the meta tags from React.js pages into HTML files. This allows the HTML files to be serve with the meta tags for rich link preview (sharing on social media sites).


Installations

In you react project root directory run the following to install react-helmet:

Using npm:

0
$ npm install --save react-helmet

Using yarn:

0
$ yarn add react-helmet

Usage

0
1
2
3
4
5
6
7
8
render() {
  return (
    <Helmet>
      <title>Home page</title>
      <meta name="description" content="This is simply the best home page there is!"/>
      <meta name="og:image" content="homepage.jpg"/>
    </Helmet>
  )
}

The title will be display on the browser tab as well as on the link preview on social media. The image and the description will also be on the link preview as well.



Deployment

I serve my web site using Express.js. You can serve it using other methods, but in this post I'll show you how to serve it using Express.js.

index.js

0
1
2
3
4
5
6
7
8
const app = express();

app.get('/', (_, res) => {
  res.sendFile(path.join(__dirname, 'build', '200.html'), (err) => {
    if (err) {
      res.status(500).send(err)
    }
  });
});

The changes that had to be made were:

  • instead of serving index.html on / path, serve 200.html instead.
  • change the path from /* to /. This is require to change from client side routing to server side, to serve the pre-rendered static HTML files.


Conclusion

I hope that you've learned something new today in this post. As I continue to improve my web site, I'll make posts on the improvements I've made to share my experience and journey, as well as to keep as a record of how my web site changes.