The Stack: Overview
We went with a single-page app approach on the new site for speed, control over page transitions, and an easily-maintainable and portable component architecture for the views.
On the back end we went with a bit of a gamble, starting our development last fall with the WordPress REST API before it fully arrived in Core. Even if we didn’t have the luxury of a final and stable API, the choice was pretty obvious for us. The API was at an early beta (version 3), and it had been fun following along as the team continued building the final-release version. Barring some mild changes to accommodate architecture shifts, the experience has been quite smooth. The API has served us very well, and we’ve got nothing but immense gratitude for all of the work of the REST team.
We were sure then, and are now, that the future of the REST API will be bright—but we wanted to start playing with it on our own terms before using it on customer sites. We’re super happy with our choice and with the direction that the API is taking. The only major pain point that we had is that, by default, the amount of data returned by the API is quite sizable. We had to spend a good chunk of dev time just cleaning the responses so we could be as efficient as possible with the data transfer from server to client. We are currently deliberating over how best to contribute back to the core team on this.
Due to time constraints, we did not go with the common approach of a decoupled CMS/Node.js server for the front end. We instead produce static JSON dumps of the API responses on page delivery, allowing us to avoid additional network requests on init while still serving directly from WordPress. We do have plans to move to a fully decoupled approach with sitewide no-JS support—in the meantime, we have actually found some benefits in keeping the delivery system coupled with WordPress, mainly concerning third-party plugin integration and, of course, code cohesion (for example, the front-end code is in the theme folder, not on another server we have to maintain).
Ryan McCue wisely suggested a very useful approach for this initial data dump, where we simulate a JSON API call to get the exact same response for the queried posts. Here you can see a simplified version of what this code looks like:
The core tools we employed are now common in the React world, and are things we see in use daily. They consist of Webpack for front-end module bundling, ES2015 syntax for JS modules, React for views and Redux for managing application state and providing structure. Redux has offered an especially amazing development experience; if you’re not familiar with the differences between it and Flux, this StackOverflow answer from Redux’s author, Dan Abramov, is an excellent read.
For a full front-end dependency stack you can view the package.json.
For routing we went with React Router and used React Router Redux to connect the store to browser history. Be aware that this Router Redux add-on is fairly volatile at the time of writing this. We are still on v1, and by its current v3 it has modified its API completely—twice. It is not a huge task to update, but a big enough one that we haven’t gotten around to it yet. While a rapidly-changing API may seem scary, with this particular component it is only a minimal time investment, and it appears to be stabilizing now. This component is designed to allow you to continue to write React Router calls exactly as you would without it, so the impact of API changes is literally going to be only a few lines of code where you do your bindings.
Async requests (AJAX calls) use the new Fetch API to get data from the REST API, and we also incorporate Redux Thunk into the chain. You can read more about this approach in this excellent article concerning async actions in Redux.
Integrating With the API and React
The kind of system described above forces you to reevaluate how you work with WordPress. First, any custom content tools you may use, such as our internal page builder plugin, need to be modified to extend the API response and eschew the traditional templating approaches.
Thankfully, this is extremely easy to do with the WordPress REST API. Here is a quick example of how we add some ACF-powered metadata to the API response for some of our post types. Again, this is a simplified version of our production code:
Secondly, you have to handle any third-party plugins that cannot be easily modified to work well with a React-based front end. A good example would be Gravity Forms. Here is an excellent plugin that is in heavy use by our team and many others. It works by injecting some global scripts on a page-by-page basis that interact with inline scripts embedded in the content wherever the plugin’s shortcode is used. This presents multiple problems for a React-based theme. For one, we have to dynamically inject the scripts with JS on demand when we detect a Gravity Form present in a post’s or page’s API response. Next, we have to mount/update a component for that page, parse the HTML we injected into it, harvest all inline scripts, and then finally execute them. These two functions handle clearing any “dirty” scripts from the last page and injecting the current pages if any:
Proud of that? Nope. We use it as little as possible—it’s totally not the React way. That said, it gets those scripts running. Thankfully, our page builder plugin and other custom work allows us to break up content and interactive needs into React-compatible data structures that let us avoid this in most of the system.
Still, another case of having to break out of the React box arises: this is how we inject these very Gists inside React-rendered HTML:
Now, in this case, there are React components out there for rendering Gists, but we wanted to support our legacy Gists on this site—after all, we migrated years of blog content, and we wanted old content to look great too! We also wanted easy Gist usage for the content editors anywhere inside their editorial flow for this particular widget.
Having Fun With Caching
Client side caching is nothing new, but it is one of my favorite features to work on—even if it is one that causes the most eye rolls in the project management department. In this particular case, by client side caching we mean storage of the WordPress API responses in your browser’s localStorage for pages you’ve previously visited here. Ideally, we should never execute API requests for any particular payload more than once unless we need to. It’s a pretty basic concept, but we see few handlers for this in the wild on the front end.
How we approached this is pretty simple: REST API responses are stored in the browser’s localStorage, using the route as the key and the JSON response (stringified as a whole) for the value. Since we have a limited amount of space (usually 5 MB) per site, we employ the excellent LZ-String library by Pieroxy. This allows us to save the JSON as a compressed UTF-16 string with over sixty percent reduction in size. For our site’s size this means we could pretty well store the bulk of our website’s data in localStorage, and make the experience for frequent users much snappier while reducing quite a bit of server load. The UTF-16 encoding does have the effect of rendering the value entry as alien gibberish and hanging Chrome’s DevTools when attempting to view it there, so be warned. (Firefox is fine.)
Of course we also have to handle invalidation. Along with the route/entry pairs we store a last-visited timestamp every time your local cache is updated. The first time you land on our site we check for this timestamp and, if found, after the page has rendered we send the endpoint this timestamp and get back a list of IDs and routes for all posts updated since your last visit. Those are then scrubbed from your browser cache. First page-loads for a session always update cache, since we have the fresh data there anyways.
On the server, the implementation for this new custom endpoint is pretty simple (once again, not showing dependency injection and other stuff for brevity’s sake):
On the topic of caching on the server side, while we can’t dive into much detail right now, our own Luca Tumedei wrote an awesome plugin to cache the response for all REST API endpoints, and to handle proper invalidation automatically. We’re working on the codebase to get it to a place where we’re comfortable sharing it—we will be open sourcing it and writing more about it on this blog, so stay tuned.
We love WordPress more than ever. The WordPress REST API is awesome. React and Redux make us happy. Get it all running together smoothly and you have a seriously enjoyable system to work in, with some well-thought-out systems in place. You also have an almost limitless supply of add-ons and plugins for both WordPress and React to test-drive. On the flip side, a lot of those “legacy” WordPress plugins are going to take some very special work to get running inside a React theme, and you’re going to have to handle porting any existing tools you have to the WordPress REST API for data delivery. And as we all know, the landscape is moving at 88 miles per hour. Pick your dependencies carefully, and expect a ten percent “whoops!” rate six months down the road.
Whether or not that’s worth it for you and your team is a debate with different outcomes on a case-by-case basis. We’re still trying to figure out where we stand on this debate. For the time being, we’re confident that this is the right approach for our team when we need to implement complex or interactive applications, and we’re investing in learning it. And some of our current projects are starting to see this framework implemented for some specific functionalities. As for implementing 100 percent React-based themes for content sites like we did here, we’re not sure it’s the right solution yet. There are years upon years of reusable code and boilerplate in the land of the good ol’ PHP template, which is hard to argue against.
For now, we have a bucket of takeaways and reusable code as a result of this adventure; we have current service projects that will benefit from this code and the tools it generated; and we’ve got a better team for it. In our opinion, it’s only natural that as WordPress itself heads in this direction—and front-end templating needs get more demanding and interactive—we embrace the technologies that facilitate that and continue to forge ahead, both inside WordPress and beyond.