About a year and a half ago we built an internal tool for company-wide announcements. We had originally built the back-end in Phoenix and the front-end in React, taking advantage of Redux and Phoenix channels to deliver updates to the browser in real-time.

This made for a great real-time experience but it also slowed down the pace of development and caused fewer people to contribute. About three months ago we decided that it was time to ditch React and go back to server-side rendering.

Real-time updates in the browser when other users interacted with the application made for a great experience, but it had some additional costs.

Instead of building out the feature in one place we had to build it out in the API and the UI. This also meant that anyone who wanted to contribute had to know React and Phoenix which discouraged others from trying to contribute.

Another pain point with the React codebase was testing. Since the application and the React client were separate, it meant that we had to make sure our fake server responses were always up to date. In practice this wasn’t a large problem, but it did burn us a few times and reduced our confidence in the tests.

Finally, we wanted more people to contribute. We found that having to build out a feature in two places is a lot more work than building it out in one and trying to coordinate the front-end and the back-end for the feature is tedious. This is especially true since the work on this application is done during our investment time.

Since we already had a namespaced API in place we were able to rewrite the pages in Phoenix and deploy to production without affecting the existing React front-end. Since the app is largely CRUD most pages were a quick one-to-one copy of the HTML replacing className with class and replacing {} blocks with <%= %> .

In the places we needed JavaScript, we took the tried-and-true path of “sprinkling” on the JavaScript. The best example of this is live-updating comments. Whenever a comment is created on the back-end we broadcast to all users that the comment was created. Instead of sending JSON, we created a live-html channel where we broadcast HTML updates to users. Here’s the JavaScript code straight from the application.

import socket from ' ./socket ' const channel = socket . channel ( ' live-html ' , {}) channel . join () . receive ( ' ok ' , function ( resp ) { console . log ( ' Joined successfully ' , resp ) }) . receive ( ' error ' , function ( resp ) { console . log ( ' Unable to join ' , resp ) }) channel . on ( ' new-comment ' , payload => { $ ( `[data-announcement-id=' ${ payload . announcement_id } '] .comments-list` ) . append ( payload . comment_html ) })

That’s a surprisingly small amount of JavaScript that powers a huge piece of functionality in our application. This strategy of generating some HTML on the server and broadcasting it to clients is much simpler than writing your entire front-end in JavaScript for the same functionality.

We also added Turbolinks to the app which makes page transitions nearly seamless and let us keep that single-page app feel.

Overall the migration was relatively painless. We ended up having more people contribute to the project in a few months than we previously had in a year with the React front-end. The tests are much easier to write while also being much more reliable since we didn’t have to rely on the payload the back-end sends matching up with the payload we used in tests.

Although a lot of people in the company knew the migration was happening we didn’t tell anyone when we pushed it to production. Not a single person mentioned seeing a difference or knew that the application they were using was no longer powered by React. This was because the time to load pages was greatly reduced thanks to Phoenix’s speed and not having to bootstrap a large amount of data on the front-end via React.

In the end we realized and proved to ourselves that we can write Phoenix server-rendered applications that are just as great as front-end framework-powered single page applications. Not only is the end product just as great — we can write features faster, have more confidence in our tests, and more easily have other developers contribute to the application. Altogether I think this was a huge win.

What projects have you removed front-end frameworks from recently?