Making head tag management thread-safe with react-head

Jeremy Gayed
On Frontend Engineering
3 min readOct 11, 2017

--

React 16 has landed bringing about some major under-the hood improvements as well as some really exciting new features such as Error Boundaries (componentDidCatch()) and what I’m really amped about, React Portals.

TL;DR

This post talks about the result of my recent experimentation with React Portals: react-head. A thread-safe (supports asynchronous server rendering)<head /> tag management library.

Portals

Portals allow you to render React nodes outside of the root React mount point. A common use case for this is to render page modals, such as what react-modal will be doing soon.

Portals are intuitive and easy to use, and best of all, the event bubbling works as if they were rendered as direct descendants in your component hierarchy, very neat!

Here’s how simple it is to render a React Portal:

render() {
// instead of returning HTML markup or more Components
// you return the result of ReactDOM.createPortal()
return ReactDOM.createPortal(
this.props.children, // Any component
domNode, // Any DOM reference
);
}

Portal into your HEAD

I decided to experiment a bit with React Portals and wondered if it would be possible to render a portal into the document.head — turns out, it works just fine and just as you would expect!

And so, this is the idea behind react-head — it utilizes React Portals to render HEAD tags to document.head Valid tags include include: <title>, <base>, <link>, <style>, <meta>, <script>, <noscript>, <template> .

How does this compare to react-helmet?

React-helmet is an excellent library that we still use today at The New York Times. It has many great features (such as nested, collapsing tag support) and a fairly large community around it. However, we have come across a few issues while using react-helmet over the past several months. Specifically, since it uses react-side-effect under the hood, it does not play well with asynchronous rendering.

We learned this the hard way while experimenting with Rapscallion’s asynchronous server-side rendering for a few weeks in production. We were getting strange reports of inconsistent meta tags and article titles seen out in the wild. Thanks to some keen sleuthing by my colleague, Scott Taylor, it turns out that since the library was not thread-safe, multiple concurrent requests affected the server-rendered meta tags for each other. The only easy solution to this at the time was to turn off asynchronous rendering (and miss out on the performance benefit that came along with that) until we could reach a longer term solution.

Thread-safe meta tag management

It looks like React Portals is that longer term solution. By rendering a portal to document.head on the client, we’re able to affect changes to the DOM outside of the scope of the root React mount point. Here’s how react-head works:

  1. During server-rendering <head /> tags are collected in an array.
  2. After rendering the app, this array is renderToString()’d and then placed in the server template <head /> block.
  3. Then, on the client, the server-rendered tags are querySelected and removed.
  4. The removed tags are replaced by the ReactDOM.createPortal() version of these tags, in order to support SPA functionality from then on.

You can see an example app of react-head usage here.

Also a shout out to Dan Abramov who helped me resolve and fix a dev warning in an early build of react-head.

What’s next

At The Times, we’re still in the middle of transitioning key pieces of our stack (e.g. moving from Relay to Apollo — a separate post on that to come soon), before we can upgrade to React 16, but we hope to make the upgrade very soon. Once we do, I anticipate that we’ll be able to replace our use of react-helmet with react-head, especially to get around asynchronous rendering issues and be better prepared for React 17, where asynchronous rendering is anticipated to be the default rendering mode on the server.

I also hope to gather feedback from any early adopters who are looking for a thread-safe meta tag management library. I’m looking to add feature parity with react-helmet, such as collapsing nested tags, and to address any other use cases that come up while dog-fooding the library at The Times.

As always, any feedback is welcomed. Even better, PRs are deeply appreciated. I hope react-head can be of use to you!

--

--

Coptic Orthodox Christian. Lead Software Engineer @nytimes. Lover of all things JavaScript 🤓