Table of Contents
Even in 2024, many companies still rely on legacy Rails monoliths based on older versions of Rails, such as Rails 4 or earlier. These applications often use outdated tools like jQuery (a popular library for making interactive websites) and the Rails asset pipeline (a system for managing website files like JavaScript and CSS).
Why haven’t they upgraded? For many teams, it’s not that simple. Fully switching to modern technologies can cost a lot of money and time. Plus, they’re often under pressure to release new features quickly, which means they can’t hit a pause button for a big upgrade.
Our team was in a similar situation. We only recently started upgrading our Rails app from version 4 to version 5. With limited resources and constant demands to roll out new features, it hasn’t been easy.
But here’s the thing: we’ve been using React in our Rails 4 app for over a year now. And guess what? It helped us work around many of the old system’s limitations.
In this article, we’ll share our journey. We’ll explain how we gradually moved important parts of our app to React and eventually transitioned these features to a separate Single Page Application (SPA).
This transformation turned out to be critical as this updated part of our app now generates almost half of our project’s income.
Understanding the Application and Its Challenges
Our application is a Rails 4 monolith running on Ruby 2.5.0. It relies on tools like jQuery (in its first version) and various plugins, using Rails views, ERB.js partials, and a mix of CSS methodologies.
This setup may have worked when the app was first created, but became a headache over time.
Here’s why:
- It’s hard to maintain. The codebase is huge, messy, and often undocumented. This means new developers, and even experienced ones, struggle to figure out how things work.
- Slow performance. Rails 4’s “asset pipeline” is outdated and slow. It doesn’t support modern ways to write JavaScript or CSS transpiration, making everything feel clunky.
- Limited ecosystem. Finding tools that work well with this old system is like searching for a needle in a haystack.
- Large files, slow loading. Because of its outdated setup, the app’s pages take a long time to load. Users have to wait longer, and the slow execution of JavaScript makes the experience worse.
- Styling chaos. Various styling tools and frameworks lead to a disorganized, inconsistent CSS landscape.
Exploring Potential Solutions
When faced with the challenges of our outdated Rails frontend, we considered several options. Each had its pros and cons, depending on what we wanted to achieve and the resources we had.
- Upgrading the current setup. We could modernize by updating jQuery and CSS frameworks. This would make the app easier to maintain, but it required highly skilled developers and a lot of time.
- Building a separate frontend application. Another option was to create a standalone frontend app that communicates with the Rails backend via API. While this approach is ideal for creating a modern, flexible frontend, it’s resource-intensive, which we didn’t have at the time.
- Using the Hotwire ecosystem. Hotwire is a set of tools designed to make Rails apps faster and more interactive without relying heavily on JavaScript frameworks. It includes Turbo (for fast page loads) and Stimulus (for adding interactive features). Unfortunately, Turbo doesn’t work with Rails 4. While Stimulus could handle some interactivity, it might not resonate with all frontend developers.
- Integrating modern frontend tools within the monolith. Our final option was to use bundler tools like Webpack to bring in modern frameworks such as React directly into the current Rails app. As a result, we could gradually introduce new technologies without overhauling everything at once.
Our Chosen Solution
We decided to go with the fourth option. This approach seemed the most practical, with Webpack as our bundler and React as our frontend library. It allowed us to modernize step by step while staying within our team’s capabilities.
This choice was based on several practical reasons:
- Session authentication. We didn’t need to switch to complex systems like JWT (JSON Web Tokens) or design separate user processes. Our existing session authentication approach worked well.
- Minimal backend changes. Adapting the backend to support JSON API responses required only minor adjustments.
- Frontend autonomy. With React in place, frontend developers could focus on their work without waiting for help from Rails developers.
- Modern ecosystem. We could leverage React and its extensive ecosystem of npm packages for both new feature development and the migration of legacy code.
- Flexible bundling. Webpack offered the flexibility and scalability we needed to handle both new features and legacy code.
To integrate React and Webpack with our Rails 4 application, we used react-rails (version 2.7.0) and webpacker (version 4.3.0). While these are no longer officially recommended, they were the best options compatible with Rails 4.
Benefits of This Approach
Our chosen solution gave us several advantages:
- Streamlined setup. We avoided spending time on setting up a dev server and build pipeline, thanks to Webpacker’s Ruby helper methods.
- React components in Rails. The react_component helper allowed us to integrate React components right into Rails partials. This made it possible to pass props directly between Rails and React without extra coding.
- Server-side rendering (SSR). We could achieve SSR without a separate app, allowing us to maintain performance.
- Seamless CI/CD Integration. Webpacker smoothly integrates bundling into the Rails asset pipeline, so we didn’t have to add extra steps to our CI/CD process.
We explored alternatives, such as vite_rails with inertia.js, but they had compatibility issues with Rails 4. Creating a bespoke setup without abstractions was another option but would have required excessive resources we didn’t have.
Navigating Limitations with React and Webpacker
Using React and Webpacker with Rails 4 came with limitations.
The highest supported versions of react-rails and webpacker were 2.7.0 and 4.3.0, respectively. While these allowed us to work with React 18 and TypeScript 5, Webpacker 4.3.0 was stuck with Webpack 4.
This older version of Webpack restricted us from using the latest Node.js versions and npm packages. Additionally, Webpacker’s built-in abstractions made custom setups tedious.
To address these limitations, we decided to upgrade the tools used by these gems. This involved:
- Custom webpack configs. We replaced the default Webpacker configurations with custom raw Webpack configs for each environment. We built these custom configs based on Webpacker 4’s source code.
- Upgrading dependencies. Moving to Webpack 5 allowed us to use latest plugins and tools. During this process, we removed outdated plugins and replaced them with modern, efficient alternatives.
- Manual bundle management. While we could still use the javascript_pack_tag in Rails views, switching to raw Webpack configs meant manually specifying each new bundle entry in the Webpack configuration. This was a cheap price to pay for having clear webpack configs instead of magical objects provided by webpacker.
One issue we faced was that client-side rendering worked smoothly, but server-side rendering (SSR) stopped working. At the time, we didn’t need SSR for critical features, but we knew it might be important later. To prepare, we resolved this by monkey-patching the Webpacker gem as well as adding execjs and mini_racer gem’s latest versions.
Migrating Critical Business Functionality
On the backend, we adapted existing endpoints to respond with JSON using Rails’ respond_to method. We replaced some JavaScript response endpoints with JSON-only responses while retaining the old functionality as a fallback. Feature flags allowed us to switch between the old and new UI seamlessly.
For the frontend, we set up necessary providers (like theme and react-query providers) and wrapped react-rails components in these providers. Initially, we had multiple react-rails components, which we later consolidated into a single one.
As we refined the UX/UI and added more features, we introduced react-router for client-side navigation. Eventually, we moved all critical functionality to a dedicated SPA.
Integrating Webpacker with Webpack 5 also helped us move away from the Rails asset pipeline. This change made development faster and reduced CI/CD times for asset compilation.
Transform Your Legacy Rails Monoliths With the Right Team
Stuck with an older Rails monolith? It doesn’t mean you’re out of options for upgrading your legacy frontend. There’s no need for a massive resource allocation to migrate your legacy frontend to modern technologies or to build a separate frontend application.
You can improve your app without disrupting your workflow by taking small, strategic steps. Like adding React for new features and gradually updating legacy code.
This approach lets you stay on track with your business goals while keeping costs manageable. Over time, your legacy system can evolve into a faster, more flexible and responsive application that meets today’s needs.
However, even with a solid plan, finding the right talent to handle this process can sometimes feel like a challenge.
This is where JetRuby’s Staff Augmentation service becomes a game-changer. We provide access to skilled Ruby on Rails developers who feel like an extension of your team. They understand how to approach complex legacy systems and help you solve real business problems without adding unnecessary stress.
Here’s how this service helps businesses like yours:
- Fast access to experts. Gain access to highly skilled developers within just 2–3 weeks, with the flexibility to adjust team size based on your project’s needs.
- A team that works together. Our developers work together as a cohesive unit, eliminating adaptation delays and boosting productivity from day one.
- Real experience you can trust. With over 15 years of experience and more than 200 projects completed, JetRuby’s team ensures your app modernization runs smoothly.
- Transparent costs. You receive a detailed monthly report showing exactly how many hours each developer worked and what they achieved. There are no hidden fees or unexpected charges.
- Data security and compliance. JetRuby follows the highest standards for data protection, including HIPAA compliance and ISO certification, so you can trust us with sensitive information.
- More than just development. Our services go beyond coding. If needed, we offer support with project management, business analysis, and UI/UX design to give you a complete solution.
- Flexibility when you need it. Depending on your project’s requirements, you can scale the team size up or down. This ensures you never pay for more resources than you need.
- No collaboration barriers. Our developers share U.S. working hours for at least 4 hours daily, making communication smooth and efficient.
If you need additional details or have any questions, please contact us any time you see fit!