You’ve been experimenting with React lately and you are aware that in accordance with accessibility, SEO, and performance best practices you should serve a semantic HTML document that React enhances — not creates. But how do you author an initial synchronous experience and an enhanced asynchronous experience isomorphically?
One of the most common arguments against progressive enhancement stems from the assumption that duplication is necessary to serve both synchronous and asynchronous experiences. Nobody wants to maintain wet code. DRY (Do–not–Repeat–Yourself) code is something we all should strive for. If we have to make an update in multiple areas of our codebase we increase the risk of introducing bugs. Plus we’re lazy.
But how do we author our initial HTML document and the components used to enhance it with the same source files? The answer may surprise you. If you’ve been using React on the client side, your components may be more ready to author your HTML documents than you think. In this post we’ll explore a few tips to prepare your React components for server–side usage.
One of the great things about React is that it doesn’t require the DOM as a dependency
— React Docs
React supports server–side environments, but that doesn’t mean that any React component you create is compatible out of the gate. Because your Node server is naive. It has no idea what the DOM (Document Object Model) is.
For server–side usage you need to explicitly require React in any module that utilizes it. This isn’t necessary for client side usage where you can import React in your main.js entry point and go on using React freely in any modules you require. When using React on the server side we don’t have an entry point. Our components are standalone so we need to explicitly include React. At the top of your server–side components place this conditional include:
That was easy. What else? Chances are your components may be referencing the DOM in some way. Take this ToDoForm component for example. It has an updatePageTitle method that updates the title of the page based on how many uncompleted tasks the user is presented with.
Nothing to it right? Well, not so fast. When this component runs on the server–side it will confuse our Node server so bad it just won’t know what to do. Our request will eventually time out. We don’t like it when our requests time out, so let’s help our Node server not get so confused. The issue is our server–side environment does not understand what our document object is. We will detect if our component is running on the client side, or not, and proceed accordingly.
Create a helpers.js module. It will export a boolean serverSideRendering variable that will be true if we are running on the server.
If executed from the server the try statement will fail. And that’s ok. Our IIFE (Immediately Invoked Function Expression) will return true and our Node server will move right along. If executed on the client side, the try statement will not fail and the false expression !(document !== undefined) is returned.
We can now include our helpers.js module and use it to keep our Node server from getting confused. Let’s go back to our updatePageTitle method and immediately escape out of the function if we are running on the server–side.
Your Node server thanks you. The DOM isn’t the only thing our Node server is oblivious to though.
The Performance interface represents timing-related performance information for the given page.
When our ToDoForm component mounts we want to instruct it to reach out for fresh data if there is a chance our content could be getting stale. So we’ve added a componentWillMount method that does just that:
Above we’re using the Performance interface to determine if the app has been running for more than 15 seconds. Nifty! One problem though. On the server side there is no Performance interface. Let’s save our Node server from confusion by slightly modifying our conditional.
Excellent. Our Node server gets by with a little help from our helpers.js module. As you author React components don’t hesitate to use conditionals and try catch statements to shield your Node server from the unknown.
Now that we’ve prepared our React component to shield our Node server from client–side confusions, we’re ready to render our component on the server–side. From within our Node route, we’ll get the pending tasks, pass them to the same Redux store used on the client side, and render our ToDoForm on the server–side to be sent along with the initial HTML request.
We have a simple Twig template that includes a header, body tag, the React code we’ll pass in, and our scripts:
And our Node server renders the server–side friendly ToDoForm component without a hitch:
Congratulations. You’ve leveled up your server–side rendering skills! That wasn’t so bad was it? We’ve been following along with some examples from the Sync To Do source code. You’ll find the working code and more examples there.