It’s All In the Head: Managing the Document Head of a React Powered Site With React Helmet
Publikováno: 30.10.2019
The document head might not be the most glamorous part of a website, but what goes into it is arguably just as important to the success of your website as its user interface. This is, after all, where you tell search engines about your website and integrate it with third-party applications like Facebook and Twitter, not to mention the assets, ranging from analytics libraries to stylesheets, that you load and initialize there.
A React application lives in the DOM node … Read article
The post It’s All In the Head: Managing the Document Head of a React Powered Site With React Helmet appeared first on CSS-Tricks.
The document head might not be the most glamorous part of a website, but what goes into it is arguably just as important to the success of your website as its user interface. This is, after all, where you tell search engines about your website and integrate it with third-party applications like Facebook and Twitter, not to mention the assets, ranging from analytics libraries to stylesheets, that you load and initialize there.
A React application lives in the DOM node it was mounted to, and with this in mind, it is not at all obvious how to go about keeping the contents of the document head synchronized with your routes. One way might be to use the componentDidMount
lifecycle method, like so:
componentDidMount() {
document.title = "Whatever you want it to be";
}
However, you are not just going to want to change the title of the document, you are also going to want to modify an array of meta and other tags, and it will not be long before you conclude that managing the contents of the document head in this manner gets tedious pretty quickly and prone to error, not to mention that the code you end up with will be anything but semantic. There clearly has to be a better way to keep the document head up to date with your React application. And as you might suspect given the subject matter of this tutorial, there is a simple and easy to use component called React Helmet, which was developed by and is maintained by the National Football League(!).
In this tutorial, we are going to explore a number of common use cases for React Helmet that range from setting the document title to adding a CSS class to the document body. Wait, the document body? Was this tutorial not supposed to be about how to work with the document head? Well, I have got good news for you: React Helmet also lets you work with the attributes of the <html>
and <body>
tags; and it goes without saying that we have to look into how to do that, too!
One important caveat of this tutorial is that I am going to ask you to install Gatsby — a static site generator built on top of React — instead of Create React App. That's because Gatsby supports server side rendering (SSR) out of the box, and if we truly want to leverage the full power of React Helmet, we will have to use SSR!
Why, you might ask yourself, is SSR important enough to justify the introduction of an entire framework in a tutorial that is about managing the document head of a React application? The answer lies in the fact that search engine and social media crawlers do a very poor job of crawling content that is generated through asynchronous JavaScript. That means, in the absence of SSR, it will not matter that the document head content is up to date with the React application, since Google will not know about it. Fortunately, as you will find out, getting started with Gatsby is no more complicated than getting started with Create React App. I feel quite confident in saying that if this is the first time you have encountered Gatsby, it will not be your last!
Getting started with Gatsby and React Helmet
As is often the case with tutorials like this, the first thing we will do is to install the dependencies that we will be working with.
Let us start by installing the Gatsby command line interface:
npm i -g gatsby-cli
While Gatsby's starter library contains a plethora of projects that provide tons of built-in features, we are going to restrict ourselves to the most basic of these starter projects, namely the Gatsby Hello World project.
Run the following from your Terminal:
gatsby new my-hello-world-starter https://github.com/gatsbyjs/gatsby-starter-hello-world
my-hello-world-starter
is the name of your project, so if you want to change it to something else, do so by all means!
Once you have installed the starter project, navigate into its root directory by running cd [name of your project]/
from the Terminal, and once there, run gatsby develop
. Your site is now running at http://localhost:8000
, and if you open and edit src/pages/index.js
, you will notice that your site is updated instantaneously: Gatsby takes care of all our hot-reloading needs without us even having to think of — and much less touch — a webpack configuration file. Just like Create React App does! While I would recommend all JavaScript developers learn how to set up and configure a project with webpack for a granular understanding of how something works, it sure is nice to have all that webpack boilerplate abstracted away so that we can focus our energy on learning about React Helmet and Gatsby!
Next up, we are going to install React Helmet:
npm i --save react-helmet
After that, we need to install Gatsby Plugin React Helmet to enable server rendering of data added with React Helmet:
npm i --save gatsby-plugin-react-helmet
When you want to use a plugin with Gatsby, you always need to add it to the plugins array in the gatsby-config.js
file, which is located at the root of the project directory. The Hello World starter project does not ship with any plugins, so we need to make this array ourselves, like so:
module.exports = {
plugins: [`gatsby-plugin-react-helmet`]
}
Great! All of our dependencies are now in place, which means we can move on to the business end of things.
Our first foray with React Helmet
The first question that we need to answer is where React Helmet ought to live in the application. Since we are going to use React Helmet on all of our pages, it makes sense to nest it in a component together with the page header and footer components since they will also be used on every page of our website. This component will wrap the content on all of our pages. This type of component is commonly referred to as a "layout" component in React parlance.
In the src
directory, create a new directory called components
in which you create a file called layout.js
. Once you have done this, copy and paste the code below into this file.
import React from "react"
import Helmet from "react-helmet"
export default ({ children }) => (
<>
<Helmet>
<title>Cool</title>
</Helmet>
<div>
<header>
<h1></h1>
<nav>
<ul>
</ul>
</nav>
</header>
{children}
<footer>{`${new Date().getFullYear()} No Rights Whatsoever Reserved`}</footer>
</div>
</>
)
Let's break down that code.
First off, if you are new to React, you might be asking yourself what is up with the empty tags that wrap the React Helmet component and the header and footer elements. The answer is that React will go bananas and throw an error if you try to return multiple elements from a component, and for a long time, there was no choice but to nest elements in a parent element — commonly a div — which led to a distinctly unpleasant element inspector experience littered with divs that serve no purpose whatsoever. The empty tags, which are a shorthand way for declaring the Fragment
component, were introduced to React as a solution to this problem. They let us return multiple elements from a component without adding unnecessary DOM bloat.
That was quite a detour, but if you are like me, you do not mind a healthy dose of code-related trivia. In any case, let us move on to the <Helmet>
section of the code. As you are probably able to deduce from a cursory glance, we are setting the title of the document here, and we are doing it in exactly the same way we would in a plain HTML document; quite an improvement over the clunky recipe I typed up in the introduction to this tutorial! However, the title is hard coded, and we would like to be able to set it dynamically. Before we take a look at how to do that, we are going to put our fancy Layout
component to use.
Head over to src/pages/
and open ìndex.js
. Replace the existing code with this:
import React from "react"
import Layout from "../components/layout"
export default () =>
<Layout>
<div>I live in a layout component, and life is pretty good here!</div>
</Layout>
That imports the Layout
component to the application and provides the markup for it.
Making things dynamic
Hard coding things in React does not make much sense because one of the major selling points of React is that makes it's easy to create reusable components that are customized by passing props to them. We would like to be able to use props to set the title of the document, of course, but what exactly do we want the title to look like? Normally, the document title starts with the name of the website, followed by a separator and ends with the name of the page you are on, like Website Name | Page Name
or something similar. You are probably right, in thinking, we could use template literals for this, and right you are!
Let us say that we are creating a website for a company called Cars4All. In the code below, you will see that the Layout
component now accepts a prop called pageTitle
, and that the document title, which is now rendered with a template literal, uses it as a placeholder value. Setting the title of the document does not get any more difficult than that!
import React from "react"
import Helmet from "react-helmet"
export default ({ pageTitle, children }) => (
<>
<Helmet>
<title>{`Cars4All | ${pageTitle}`}</title>
</Helmet>
<div>
<header>
<h1>Cars4All</h1>
<nav>
<ul>
</ul>
</nav>
</header>
{children}
<footer>{`${new Date().getFullYear()} No Rights Whatsoever Reserved`}</footer>
</div>
</>
)
Let us update ìndex.js
accordingly by setting the pageTitle
to "Home":
import React from "react"
import Layout from "../components/layout"
export default () =>
<Layout pageTitle="Home">
<div>I live in a layout component, and life is pretty good here!</div>
</Layout>
If you open http://localhost:8000
in the browser, you will see that the document title is now Cars4All | Home
. Victory! However, as stated in the introduction, we will want to do more in the document head than set the title. For instance, we will probably want to include charset, description, keywords, author and viewport meta tags.
How would we go about doing that? The answer is exactly the same way we set the title of the document:
import React from "react"
import Helmet from "react-helmet"
export default ({ pageMeta, children }) => (
<>
<Helmet>
<title>{`Cars4All | ${pageMeta.title}`}</title>
{/* The charset, viewport and author meta tags will always have the same value, so we hard code them! */}
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="author" content="Bob Trustly" />
{/* The rest we set dynamically with props */}
<meta name="description" content={pageMeta.description} />
{/* We pass an array of keywords, and then we use the Array.join method to convert them to a string where each keyword is separated by a comma */}
<meta name="keywords" content={pageMeta.keywords.join(',')} />
</Helmet>
<div>
<header>
<h1>Cars4All</h1>
<nav>
<ul>
</ul>
</nav>
</header>
{children}
<footer>{`${new Date().getFullYear()} No Rights Whatsoever Reserved`}</footer>
</div>
</>
)
As you may have noticed, the Layout
component no longer accepts a pageTitle
prop, but a pageMeta
one instead, which is an object that encapsulates all the meta data on a page. You do not have to do bundle all the page data like this, but I am very averse to props bloat. If there is data with a common denominator, I will always encapsulate it like this. Regardless, let us update index.js
with the relevant data:
import React from "react"
import Layout from "../components/layout"
export default () =>
<Layout
pageMeta={{
title: "Home",
keywords: ["cars", "cheap", "deal"],
description: "Cars4All has a car for everybody! Our prices are the lowest, and the quality the best-est; we are all about having the cake and eating it, too!"
}}
>
<div>I live in a layout component, and life is pretty good here!</div>
</Layout>
If you open http://localhost:8000
again, fire up DevTools and dive into the document head, you will see that all of the meta tags we added are there. Regardless of whether you want to add more meta tags, a canonical URL or integrate your site with Facebook using the Open Graph Protocol, this is how you about about it. One thing that I feel is worth pointing out: if you need to add a script to the document head (maybe because you want to enhance the SEO of your website by including some structured data), then you have to render the script as a string within curly braces, like so:
<script type="application/ld+json">{`
{
"@context": "http://schema.org",
"@type": "LocalBusiness",
"address": {
"@type": "PostalAddress",
"addressLocality": "Imbrium",
"addressRegion": "OH",
"postalCode":"11340",
"streetAddress": "987 Happy Avenue"
},
"description": "Cars4All has a car for everybody! Our prices are the lowest, and the quality the best-est; we are all about having the cake and eating it, too!",
"name": "Cars4All",
"telephone": "555",
"openingHours": "Mo,Tu,We,Th,Fr 09:00-17:00",
"geo": {
"@type": "GeoCoordinates",
"latitude": "40.75",
"longitude": "73.98"
},
"sameAs" : ["http://www.facebook.com/your-profile",
"http://www.twitter.com/your-profile",
"http://plus.google.com/your-profile"]
}
`}</script>
For a complete reference of everything that you can put in the document head, check out Josh Buchea's great overview.
The escape hatch
For whatever reason, you might have to overwrite a value that you have already set with React Helmet — what do you do then? The clever people behind React Helmet have thought of this particular use case and provided us with an escape hatch: values set in components that are further down the component tree always take precedence over values set in components that find themselves higher up in the component tree. By taking advantage of this, we can overwrite existing values.
Say we have a fictitious component that looks like this:
import React from "react"
import Helmet from "react-helmet"
export default () => (
<>
<Helmet>
<title>The Titliest Title of Them All</title>
</Helmet>
<h2>I'm a component that serves no real purpose besides mucking about with the document title.</h2>
</>
)
And then we want to include this component in ìndex.js
page, like so:
import React from "react"
import Layout from "../components/layout"
import Fictitious from "../components/fictitious"
export default () =>
<Layout
pageMeta={{
title: "Home",
keywords: ["cars", "cheap", "deal"],
description: "Cars4All has a car for everybody! Our prices are the lowest, and the quality the best-est; we are all about having the cake and eating it, too!"
}}
>
<div>I live in a layout component, and life is pretty good here!</div>
<Fictitious />
</Layout>
Because the Fictitious
component hangs out in the underworld of our component tree, it is able to hijack the document title and change it from "Home" to "The Titliest Title of Them All." While I think it is a good thing that this escape hatch exists, I would caution against using it unless there really is no other way. If other developers pick up your code and have no knowledge of your Fictitious
component and what it does, then they will probably suspect that the code is haunted, and we do not want to spook our fellow developers! After all, fighter jets do come with ejection seats, but that is not to say fighter pilots should use them just because they can.
Venturing outside of the document head
As mentioned earlier, we can also use React Helmet to change HTML and body attributes. For example, it's always a good idea to declare the language of your website, which you do with the HTML lang
attribute. That's set with React Helmet like this:
<Helmet>
/* Setting the language of your page does not get more difficult than this! */
<html lang="en" />
/* Other React Helmet-y stuff... */
</Helmet>
Now let us really tap into the power of React Helmet by letting the pageMeta
prop of the Layout
component accept a custom CSS class that is added to the document body. Thus far, our React Helmet work has been limited to one page, so we can really spice things up by creating another page for the Cars4All site and pass a custom CSS class with the Layout
component's pageMeta
prop.
First, we need to modify our Layout
component. Note that since our Cars4All website will now consist of more than one page, we need to make it possible for site visitors to navigate between these pages: Gatsby's Link
component to the rescue!
Using the Link
component is no more difficult than setting its to
prop to the name of the file that makes up the page you want to link to. So if we want to create a page for the cars sold by Cars4All and we name the page file cars.js
, linking to it is no more difficult than typing out <Link to="/cars/">Our Cars</Link>
. When you are on the Our Cars page, it should be possible to navigate back to the ìndex.js
page, which we call Home. That means we need to add <Link to="/">Home</Link>
to our navigation as well.
In the new Layout
component code below, you can see that we are importing the Link
component from Gatsby and that the previously empty unordered list in the head element is now populated with the links for our pages. The only thing left to do in the Layout
component is add the following snippet:
<body className={pageMeta.customCssClass ? pageMeta.customCssClass : ''}/>
...to the <Helmet>
code, which adds a CSS class to the document body if one has been passed with the pageMeta
prop. Oh, and given that we are going to pass a CSS class, we do, of course, have to create one. Let's head back to the src
directory and create a new directory called css
in which we create a file called main.css
. Last, but not least, we have to import it into the Layout
component, because otherwise our website will not know that it exists. Then add the following CSS to the file:
.slick {
background-color: yellow;
color: limegreen;
font-family: "Comic Sans MS", cursive, sans-serif;
}
Now replace the code in src/components/layout.js
with the new Layout
code that we just discussed:
import React from "react"
import Helmet from "react-helmet"
import { Link } from "gatsby"
import "../css/main.css"
export default ({ pageMeta, children }) => (
<>
<Helmet>
{/* Setting the language of your page does not get more difficult than this! */}
<html lang="en" />
{/* Add the customCssClass from our pageMeta prop to the document body */}
<body className={pageMeta.customCssClass ? pageMeta.customCssClass : ''}/>
<title>{`Cars4All | ${pageMeta.title}`}</title>
{/* The charset, viewport and author meta tags will always have the same value, so we hard code them! */}
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="author" content="Bob Trustly" />
{/* The rest we set dynamically with props */}
<meta name="description" content={pageMeta.description} />
{/* We pass an array of keywords, and then we use the Array.join method to convert them to a string where each keyword is separated by a comma */}
<meta name="keywords" content={pageMeta.keywords.join(',')} />
</Helmet>
<div>
<header>
<h1>Cars4All</h1>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/cars/">Our Cars</Link></li>
</ul>
</nav>
</header>
{children}
<footer>{`${new Date().getFullYear()} No Rights Whatsoever Reserved`}</footer>
</div>
</>
)
We are only going to add a custom CSS class to the document body in the cars.js
page, so there is no need to make any modifications to the ìndex.js
page. In the src/pages/
directory, create a file called cars.js
and add the code below to it.
import React from "react"
import Layout from "../components/layout"
export default () =>
<Layout
pageMeta={{
title: "Our Cars",
keywords: <a href="">"toyota", "suv", "volvo"],
description: "We sell Toyotas, gas guzzlers and Volvos. If we don't have the car you would like, let us know and we will order it for you!!!",
customCssClass: "slick"
}}
>
<h2>Our Cars</h2>
<div>A car</div>
<div>Another car</div>
<div>Yet another car</div>
<div>Cars ad infinitum</div>
</Layout>
If you head on over to http://localhost:8000
, you will see that you can now navigate between the pages. Moreover, when you land on the cars.js
page, you will notice that something looks slightly off... Hmm, no wonder I call myself a web developer and not a web designer! Let's open DevTools, toggle the document head and navigate back to the ìndex.js
page. The content is updated when changing routes!
The icing on the cake
If you inspect the source of your pages, you might feel a tad bit cheated. I promised a SSR React website, but none of our React Helmet goodness can be found in the source.
What was the point of my foisting Gatsby on you, you might ask? Well, patience young padowan! Run gatsby build
in Terminal from the root of the site, followed by gatsby serve
.
Gatsby will tell you that the site is now running on http://localhost:9000
. Dash over there and inspect the source of your pages again. Tadá, it's all there! You now have a website that has all the advantages of a React SPA without giving up on SEO or integrating with third-party applications and what not. Gatsby is amazing, and it is my sincere hope that you will continue to explore what Gatsby has to offer.
On that note, happy coding!
The post It’s All In the Head: Managing the Document Head of a React Powered Site With React Helmet appeared first on CSS-Tricks.