It’s so simple!

Replacing React with Rails

Or, Use the Right Tools for the Job

Attention Ruby Weekly

NOTE: if you are arriving from Ruby Weekly, check out the full article here. The first few paragraphs are posted below and the full post is on, as there is apparently a Gmail bug where any domain starting with 0x is considered spam. I’ll be adjusting my blog’s domain after the traffic dies down to resolve, but in the meantime, enjoy.

tl;dr — I replaced a ReactJS application with Rails + UJS. You will most likely identify this as a case where React was overkill, and I would agree. This article is an exploration of the decisions and events that lead to that realization, in the hopes that it might benefit others.

Additionally, React is awesome and has transformed what is possible in frontend web development. There are obvious use cases and obvious times where it is unnecessary. And sometimes its necessity is ambiguous

Five months ago I launched Basic Man, a website providing a curated list of mens essentials (shampoo, razors, cleanser, toothpaste, etc.) in an easy-to-browse and easy-to-order format. The website provides shopping cart functionality, powered by Amazon, where users can quickly add items from the list into a “remote” cart on When they are ready to complete their purchase, they are redirected to Amazon with the items pre-filled into their cart. This is a feature provided by Amazon’s API.

I wrote an article on some of the technical aspects of building Basic Man. I was quite happy with how the site was architected — the summary is:

  • The “data” was stored in a YAML file that was fed into the Middleman static site generator to build a static site
  • Smooth page transitions where accomplished using SPFjs, a library similar to pjax or Turbolinks written by YouTube that updates page elements on navigation based on a special JSON response format from the remote server
  • Middleman pre-generated all the SPF JSON files, so it could continue to be a static site
  • A Node.js API server provided a bridge between the Amazon API and the website, and additionally managed a session cookie (to persist the remote cart ID)
  • For dynamic elements (the add to cart buttons, the cart count in the header, and the cart page), I used React components that talked to the API server and mounted / unmounted them on page transitions
  • Due to the fact that the page was not fully reloading on navigation transitions, the overall application state was able to be persisted across transitions, avoiding the potential flash-of-unloaded-content

This overall architecture served the application well. The bulk of the website was hosted on GitHub Pages, meaning I had less to worry about as far as hosting goes, the site was launched, and users were able to purchase products! However, there were a few issues, with the first and foremost being SEO.

I blame the SEO issues squarely where they belong: on myself. Based on how I configured SPFjs, Google’s crawler saw every navigation as a replacement of the current page, rather than a new page transition. As such, Google saw the entire website not as 50 distinct pages, but as one page. Somewhat humorously, for the first few months the Google crawler would terminate on different pages during its crawl, leading Google to “rotate” which page it though contained the homepage content.

Additionally while the website “worked” as designed, but was not very flexible. As in, every piece of the architecture was essential in supporting every other piece to make the site work. SPFjs was a requirement because the React state needed to be persisted across page navigations. Middleman’s JSON output was required for SPFjs. React was required for interactivity. The API server was required because the app server was static. And so on. It was a tech stack house of cards. This made it difficult to solve other issues like the SEO-impacting page transitions.

I decided it was time for a rewrite.

Continue Reading on

This post originally appeared on, which was hit by a Gmail bug that flags as spam any domain starting with “0x”. The full post still appears on