During our search we had a few criteria:
Plays well with a largely server side rendered application
Can be mounted in isolation
Allows for simple unit testing
Could be easily taught to developers of all levels
React was and is a great fit for our current application. We needed isolated components that could encapsulate complex view logic that could be injected anywhere on the page. We’ve had great success with Ember in the past for standalone applications, but React’s composability provided a better model for allowing us to grow into JS components piece by piece.
The Switch to Webpack
It took us a few iterations, but eventually we landed on Webpack as our standalone asset build tool. Webpack provided all of the tooling we needed and along with plugins like webpack-manifest we were able to easily compliment the asset pipeline conventions established by Rails.
How we React
Using our helper we generate a bit of DOM that is later hydrated by our client side rendering engine. Using this approach we can still inject properties from the server side rendered view, which allows us to build dynamic components that don’t yet need an API to receive the data they need. This provides a fantastic bridge for us to sprinkle in bits of React where needed.
I’ve included here an example of a Rails view helper, a client side renderer, and the breadcrumbs we leave server side to bridge the server to client side gap.
Our view helper looks a whole lot like the one from react-rails. No reason to re-invent something that works. We use this to build a blank div with some data properties that the client side renderer will use to replace with actual React components.
Rails View Helper We use this helper alongside our normal Rails rendered views, which allows us to mix in React components as we’re adding UI interactivity. It also allows us to iterate into larger React components without having to replace the entire server side rendered view in a big bang release.
The helper renders a small snippet into the DOM, which becomes a placeholder for our React client side renderer.
DOM Snippet Waiting for the React Renderer After our view is rendered server side, our client side renderer scans the DOM for divs that are waiting to be hydrated by actual React components. These components need to be accessible via window, but we take care to namespace these entry components to prevent collisions. More complex components will usually just use the react-router as its entry point where components are rendered based on the current route.
Client Side React Renderer With these tools in place we had an adaptable platform that allowed us to iterate into client side rendered views where appropriate, replace hard to test jQuery interactions, and as a side effect gave us a great platform to do development in. I can’t express enough, especially in a large Rails application, how empowering tools like Hot Module Replacement, a real module system, spectacular linting tools, and robust testing frameworks are to our ability to iterate quickly on interactions and designs with confidence.
What was that?! Flicker?! I miss Rails views :’(
React’s view abstractions were a considerable improvement over jQuery and its simple API meant quick adoption among the developers here at Reverb. Soon we went from a few isolated components to entire views completely composed of React components. This is when we hit some issues.
On those more complex views where the header and footer were server side rendered, but the body was predominately React rendered, we had to wait and wait and wait as React took control of the DOM and rendered in our components. This can cause some jarring UI experiences.
Even where we carefully planned for good empty states we were left with UI components that flickered into existence and caused the DOM to shift around as views were hydrated. This can be mitigated by guessing at div sizes and creating good empty containers and we even went so far as to server side render our empty states with Rails only to have React remove those empty divs as they mounted, but this was far from ideal. It meant maintaining identical DOM partials in both React components and Rails’ views and clever use of jQuery to remove those empty divs once the React component was in place.
How I Learned to Stop Worrying and Love Node.js
Now that our JS was being shipped to our frontend in bundles produced by Webpack, we figured we could deliver that same bundle to a Node.js application to help out our Rails backend in rendering some server side React. React comes with server side rendering support out of the box , but we had to do some extra work to get our routed components to work well both client and server side.
At Reverb, we’ve been using react-router for client side routing and we didn’t want to have to maintain yet another routing stack server side. Luckily after a bit of finagling, cursing at docs, and ample finger crossing we were able to put something together that worked perfectly on both sides of the stack.
While we ship the same Routes.jsx file to the server and client, the server side code uses a slightly different (non browser history based) router to accomplish the same task as our client side Router.
The React Rendering Engine
Returning Results from Rails
While this simple Node.js app could render our React components we still needed a way to get these back into our Rails views. With a few modifications to our `react_component` helper we were able to call through to Node.js when needed.
Since we deploy our React Rendering Engine alongside our core Rails stack, we utilize a socket connection to communicate between the two frameworks. This separation of concerns allows our React Rendering Engine to be shared between a variety of different stacks and languages as we grow.
With these pieces in place we can serve React snippets from our backend that are later picked up by our client side renderer. This means a dramatic difference in the UX for those views that were predominately rendered via React without forcing us to use arbitrary blank containers divs. In fact we can deliver many side effect free components (ie. those that contain no external API calls) instantly to the user.
Flicker be gone!
React has been a great boon to view development here at Reverb. With React components we can easily unit test our views, close the iteration gap with Hot Module Reloading, and provide isolated re-usable components across our application platforms. Work like this gets us closer to creating the kind of experience we want for our users.
Have some questions? Feel free to reach out. We’ll be evaluating extracting some of these components into open source projects in the coming weeks so keep your ear to the ground, you might like what you hear.
This work was the result of some great efforts from: