TRBL’s Server Side Rendered React + WordPress REST API Boilerplate
Building a foundation for a SSR, code-split Create React App client using Redux paired with the WordPress REST API
UPDATE 01/08/2021:
I wrote this post in 2018 and at the time, we were working with WP because we had no other choice.
Now we do. We built a CMS of our own that does everything we liked about WP and ACF, but doesn’t need to “shoe-horn” WP into doing a bunch of things it was never supposed to do any longer.
Payload is a headless CMS and application framework that’s built with a JavaScript stack from top to bottom: Node, Express, React, MongoDB. Working with it is so many thousands of times better than any other CMS on the market and works extremely well with a JAMstack.
Features:
- GraphQL, REST, and Local APIs
- Easily customizable ReactJS Admin
- Fully self-hosted
- Extensible Authentication
- Local file storage & upload
- Field-based Localization
- Block-based Layout Builder
- Extensible SlateJS rich text editor
- Array field type
- Field conditional logic
- Extremely granular Access Control
- Document and field-level hooks for every action Payload provides
- Built with Typescript & very Typescript-friendly
- Intensely fast API
- Highly secure thanks to HTTP-only cookies, CSRF protection, and more
Give it a shot!
npx create-payload-app
—
UPDATE 04/22/2020:
Our react-wp-rest
boilerplate has been deprecated. We’ve learned a lot and made the jump to NextJS.
Here’s an updated boilerplate:
— — — —
Wow, that tagline is seriously packed with buzzwords. If I had read that in 2012 I would have hit the road.
Over the years, my design and web development studio TRBL has learned a lot about what works and what doesn’t work when developing market-facing websites built around content management systems for our clients. We’ve run the gauntlet of CMS’s and have never quite been satisfied with the amount of restrictions and impositions involved with any of them.
In today’s current web climate, it’s become much more complicated — and even more difficult — to deliver a solid website that’s up to par with 2018+ standards vs. how it was back in the day.
Ever crack open the node_modules
folder for a little excursion, just to see what your little micro-app might rely on behind the scenes?
It seems like hopping on Hacker Noon basically guarantees that you’ll leave with some sort of identity crisis or heightened case of imposter syndrome.
WTF? Shadow DOM? I thought I knew about HTML and CSS — if nothing else. What? People are hating on SASS now? When will it stop? Back up. What?
(I’m super excited about the Shadow DOM — but again, not the topic of this specific story. At the same time I also have some shadowy thoughts about the Shadow DOM — but I gotta concentrate. One thing at a time, Jim.)
The web moves fast and we all know that. But one part of the web hasn’t been moving as fast as everything else — the state of content management systems.
WordPress
At TRBL, we’ve got a lot of clients that know and love WordPress. They’re used to the admin interface, and let’s be honest with our jaded selves, if you weren’t a web developer and you just wanted to manage some content, the admin panel is pretty good. Especially when paired with Advanced Custom Fields.
We could try to recommend something else to spare ourselves from the sanity-robbing frontend development process inherent to WordPress, but what else would we even recommend? Craft? Yay, same thing, different syntax. Still no place for the newer build processes or techniques that are commonplace throughout the rest of the internet.
To a developer looking to build something with 2018 practices and benefits, most traditional CMS are all horse shit — just different flavors of horse shit. One horse just ate some hay, and one horse just ate some other horse’s shit. Who knows. Same thing, slightly different taste. Content gets coupled to the code that displays it through whatever templating system is being used and you gotta do things their monolithic, geriatric way.
It’s time to take the content out of the code. Give marketers an admin panel and give developers the freedom to build a site that won’t become unmanageable in 6 months.
Enter the idea of a headless WordPress.
TRBL is a huge proponent of the concept of a headless CMS, but unfortunately we aren’t big on the idea of paying someone else a monthly fee so that we can poll their APIs for our clients’ content. Most (if not all) widely-used and supported headless content management systems as of April 2018 unfortunately fall into this bucket. We want our projects built on open-source foundations and we want to host our own stuff.
Turns out as you probably know, a while back, WordPress integrated a REST API directly into its core. We’ve vetted it at TRBL through multiple production projects paired with a React client side, and although it’s not perfect, it does offer the ability to use WordPress as a headless CMS! Clients still get their familiar interface, and we get to build fast, maintainable web platforms that don’t remind us of Geocities. Props to WP here — hopefully they continue to see the light.
Not so fast, buddy
Firing up create-react-app
and making some calls to the WP REST API isn’t really all that difficult in and of itself. It’s actually pretty satisfying to get everything all up and running.
But then — you share a link to your new fancy site in Slack and see no meta data.
Even if Google does render JS before caching results, we still have the problem of other search engines and services that don’t. Facebook itself doesn’t even render React code before caching pages when you go to share a link. Ironic? Kinda.
Outside of proper page scraping by the big birds out there, a few other drawbacks of simply serving a JS bundle to a client come to mind. I’m not going to go too deep into this because it’s been beaten to death elsewhere on the internet, but another potential drawback of simply serving the build
folder from your fancy JS framework is the time delay added by first serving barebones HTML, then downloading a potentially large JS file, and then finally evaluating all that JS to get the results of your view.
If we want all sorts of 2018 web dev fanciness in 2018, we need to jump through some hoops. The hoops aren’t that difficult or that involved, but it gets tiring to wire up the same thing over and over again. That’s why we’ve done it for you.
TRBL’s SSR, Code-Split React + Redux + WordPress REST API Boilerplate
Why our own boilerplate?
In our quest for SSR knowledge, we’ve come across some far smarter people than we are doing similar things with server-side rendered React.
Next JS
From what we’ve seen playing with Next, it’s a beautiful concept. Declare all your API requests the Next way and they’ll run synchronously so you can return full HTML from your Node server. Voila, do whatever you want with React, render it and send it straight from the server, and have it pick up where the server left off on the client. Cool!
Postlight in NYC has built a React + WP boilerplate of their own that is absolutely amazing. It’s got some really cool features like using Robo to set up a headless WordPress installation for you with one command. They give you the full bird — a Next JS + headless WordPress setup right out of the box. It’s neat and we really look up to Postlight as a company.
But, there are a few things with Next JS that we aren’t particularly fond of yet at TRBL. Persisting components across routes is quite a bit more difficult than it should be and from what we have been able to tell, persisting kinda goes against the architecture of NextJS in general because the entirety of the top-level component re-renders every time a route changes.
For us, this negates some of the most important benefits of using a library like React. We love the idea of having components like the header persist (not re-render) between pages. We want to build nice page transitions that provide an extra layer of polish to a company’s image (and not fake them by cleverly hiding page reloads). When changing pages, or views, we want to only ask the server for what we need, rather than asking for the full HTML of a different page (which would be about 90% the same as the HTML we already have). Just seems backward.
It looks like Zeit is working on this but as of Apr 2018 an approach seems to still be in flux. Check here for more information.
Create React App + React Router
When we first started experimenting with Next, we were still learning the behind-the-scenes intricacies of React Router. Wait, weren’t we just beginning to get comfortable with how to route in React, the way that most people route in React? Now Next has its own way? Shoooooot. See back to the beginning of this story.
We’ve actually come to like React Router. At first, coming from an Angular background it was a bit bewildering, but now we want it to stick around for a while.
We also really like CRA. It’s got a lot of good stuff bundled in that makes building React apps much more efficient. Yes, we could write up our own Webpack setup and not even rely on CRA, but… been there, done that. We feel CRA is a good place to be OK with taking a shortcut.
The decision was made
Time to do some research and get to work. Over the years, much has been said about server-side rendering React apps, and lots of it can be pretty scary. But another super genius by the name of Andrei Duca wrote up a very helpful Medium story that outlines exactly how to get up and running with SSR and code splitting a React app:
Rather than rewriting his instructions and how they applied to our boilerplate, just check out his posts. It’s very worth the read and provided a lot of the hard work for our boilerplate.
API requests — run them on each render and wait, or cache them?
The super nice part about Redux on the server in our situation is that we can persist API calls within the Redux store, in memory.
We’ve chosen to architect our store with actions and a reducer that handles the loading of data needed per view, including meta needed by react-helmet
. This data gets matched to a page, post or custom post type slug from WordPress, and this way, it can be stored and fetched very easily.
The first time a user hits a page — let’s say the home page — there will be nothing stored in Redux for that specific page’s slug. But, when our server-rendered React fires, it’ll make the API calls to WP, fetch the data for the view, and store it in Redux. On this first page load, the client will pull up a server-rendered HTML file, but the API call, being asynchronous, will not have returned by the time the HTML is sent from the server. For this first load, the client will be responsible for pulling the data from the API.
But, that API call made by the server will end up returning at some point, and when it does, it’ll store itself in the server’s memory (Redux store) for all following requests.
For now, we are not caching requests in any way other than in the Redux store because we view the entirety of our SSR efforts as just measures to decrease initial load time and create meta-filled, shareable HTML. If for some reason our Node server restarts, and some lucky winner hits a page for the first time without having been cached to memory prior, boo hoo. This isn’t life or death and the user’s client will still pull content, albeit 150ms slower.
That being said, it wouldn’t be hard to modify our server renderer in an effort to synchronously pull all necessary API requests before responding with the rendered HTML if that sorta thing sounds better to you.
Building routes
In addition to storing the data needed per page in our Redux store, we also store a list of pages available so that we can build the appropriate routes. In Postlight’s implementation, and patterns commonly seen with NextJS in general, each page route path is prefixed with a /page/
segment, which is necessary to use URL parameters as identifiers within Express for what content needs to be loaded (and not conflict with other types of data such as posts). We can avoid that prefix by simply building a route within our WP theme that sends a list of all pages with their respective template and slug.
We then store the results in Redux, in an object where the key of the property is set to the slug of the data.
From our page list, we can programmatically build routes that will match exactly to page slugs, on both the server and the client — with the exact same code. We can even support pages nested within parents this way (example: /company/careers
. React Router is great.
You may have noticed that the same component LoadTemplate
is being used above for both posts and pages. We’ve wired up our routes this way because we will have a variety of different templates in use by our pages, and each page will need its own data. LoadTemplate
is responsible for both of these duties.
Crack that sucker open — it should be fairly easy to read. Compare it with Andrei’s post (above) about code splitting to make sense of the use of Loadable
within LoadTemplate
. Basically, we’ve opted to split our codebase on a template basis and that’s all handled, in addition to fetching data, within LoadTemplate
.
Sass
Everybody seems to be going back and forth over the value of Sass in a component-based architecture — but we still use it at TRBL and we’ve included a workflow for it in this boilerplate. Nesting is beautiful and we find that we still have to have some semblance of a base stylesheet to care for HTML generated from WYSIWYGs within the CMS used. You write a base stylesheet without Sass variables or functions— I dare ya. We aren’t trying to return to those days in too much of a hurry.
That being said, do what you must with this boilerplate. We tried to stay as small and out of the way as possible. Strip out the Sass, eject and introduce CSS modules if you’d like. Create React App’s got great documentation on how to get up and running with a variety of CSS approaches and I recommend reading their documentation for more information.
The internet is big and full of really smart people
We are permanently in a state of awe regarding how downright prodigal our peers in this industry can be and we’re always looking for good discussion, feedback and help. We’re releasing this as a work in progress and there are bound to be improvements to be made. We plan to continue to update and expand on this repo moving forward and we welcome pull requests. If you’ve got an idea or suggestions for improvements, lay it on us — we’d love to hear.
Questions
Leave a response! We’ll write back!