Recently, I have been working on a migration project for a client that has presented a number of interesting challenges. In this blog post, I will identify some of the challenges we have faced on this project and discuss the solutions we developed to combat them.
The Benefits of the React Framework
For this project, we debated two strategies for the migration – a full rebuild and cutover in which the site would be fully rebuilt before being released, and a more gradual migration in which we would release pages in the new framework one at a time. We decided to go with the gradual migration strategy because this allows us to get new code into production as fast as possible.
In order to support two frameworks with the same application, we added an NGINX proxy that handles all requests. The NGINX step steers the traffic in the correct direction, which allows us to seamlessly direct traffic to either the existing Angular/Rails pages or the new React pages. By sending requests through NGINX, we are able to change this routing with minimal effort when a new page is ready to be deployed or if we ever choose to roll anything back to the legacy code.
Next.js improves upon traditional Rails apps and Single Page Applications (SPA) in a couple of ways.
Like Next.js, Rails will also send pre rendered HTML to the client. The main difference being that a second visit to the same rails page will likely be no more performant than the first. The same queries will be made, the HTML will be pre rendered and sent back to the client. With Next.js, a second visit will execute the JS code with data you already fetched on your first visit. While requests for new data are made to the server in the background, the user can interact with the page almost immediately.
With a traditional SPA, you get the same benefit of not having to re-render the HTML on the server a second time, but your first visit to the application will often load most, if not all of your application JS code in a single page load. With Next.js, you only receive the JS relevant to the page you are visiting to keep the size of the bundle sent from the server to a minimum resulting in faster load times.
The Challenges Associated with Migrating
Every project has its unique issues. These are some of the main challenges that we encountered during this migration.
Local Project Setup
Grio’s developers have been working on this project for a number of years, which means that the libraries, tools, etc that developers used have evolved over time. Depending on what operating system the user was running when they installed various packages each developer could have a slightly different local environment setup. As we migrated toward our new NGINX proxied framework, many developers had to essentially start from scratch with their environment setups.
State Management When Switching Between Apps
At the core of a React application is state, the object that contains the data used to populate the components within your application. Effective state management is essential for minimizing network traffic and reducing the “time to interactive” metric of an application. State management is therefore a priority during any migration process to React.
One of the biggest challenges we faced during the migration process was that when a user’s browser returned to a React page following the use of an original Rails page, the state of the React page was lost. Therefore, the system had to once again render the React page from scratch, which negated many of the performance benefits that Next.js provided.
To mitigate this state management challenge, we created convenience wrappers around our page components that would prompt the application to refetch user data based on the authentication state in the shared cookie.
We also began periodically asking ourselves three main questions:
- What is the minimum amount of data that each page needs?
- What is the minimum amount of data that all pages need?
- When is the best time to fetch this data?
We found that these questions allowed us to predict where additional state management complications would occur and subsequently implement preventative measures.
Server Side Rendering (SSR)
Though Next.js pages act similar to traditional SPA’s, the challenges we encountered due to Next.js’s SSR are a bit different. Two common SRR issues that we identified during this React migration were font/asset loading issues and library compatibility issues.
Font/asset loading issues typically originate from complications with the location of your assets and how you serve them. Keeping assets in the wrong place can cause fonts to disappear or become unstyled for a moment when the page is rendered. When this issue occurred on our most recent project, I was forced to disable an optimization to correct it. I found that this is a common issue when dealing with Next.js, and there are numerous blogs and threads discussing both the challenges and the various solutions other users have found.
Library compatibility is another issue that we knew we might encounter on this project; as such, we made sure to do ample due diligence prior to beginning. For our project, we finally decided to use Ant Design because it is fully compatible with both TypeScript and SSR, which was essential for our specific application.
The library you choose can have ramifications on your SSR. For example, one of the other libraries we considered using was React Responsive. However, we found that it was not as beneficial for our system because, at mobile breakpoints, you often had to refresh the page for React Responsive to realize it was a mobile screen. Choosing the correct library from the beginning made our overall process more efficient and saved us from potential headaches throughout the migration process.
Cascading Style Sheets (CSS) and Style Scoping
For our project, we used CSS and Syntactically Awesome Style Sheets (Sass) to style the website because they were compatible with both Next.js and the old Rails framework. Our goal was to be able to reuse a lot of our existing stylesheets to expedite the process of rebuilding components. Next.js recently added built-in support for CSS modules for both CSS and Sass. Prior to this recent optimization, styles were not scoped by page and it was difficult to ensure that the correct style sheets were loaded with each page in development environments. This required workarounds to ensure that styles were appropriately applied in production.
When performing a gradual migration, it is important to have a system in place that allows users to dynamically switch between the old and new pages. This allows us to work on the React pages without negatively impacting the active content. NGINX served as the solution.
With NGINX, if we launched a React page that we didn’t like or that had a bug, we were able to efficiently change our NGINX configuration and roll it back to React without having to roll back any code from the release. Similarly, when we deployed a new page with React, we are able to use NGINX to seamlessly shift from exposing the Rails code to exposing the new React code. This allowed us to keep the website active and updating throughout the migration process.
One potential pitfall with this approach is that you must consider the ramifications of a user who has the old page loaded in their browser when you change your NGINX configuration. They could experience unusual behavior until they have refreshed and loaded the new version of the page.
One of the largest challenges to the gradual migration from AngularJS to React is navigation between the two side-by-side applications. As mentioned previously, one of the benefits of Next.js is that it acts like a single page application. This is accomplished by using a Next.js Link component for internal routes. For any navigation between Next.js pages, the Next.js Link will use it’s internal routing to prevent reloading the entire application or page.
However, this link component only works for Next.js pages. Therefore, whenever a page on the website is launched in the new React format, you have to go through all of the other React pages and relink the url in the code with the Next.js link component. This process is understandably time consuming and, on a website where there are dozens of links to the same url, it lends itself to the real possibility that links will be missed.
To address this challenge, we created a component that wraps the link and allows us to dynamically render the link type for the url as either a Next.js link or a normal anchor tag. Then, once the page is converted to React, we simply have to whitelist the new url in one location to affect all of the links to that url. While this solution required a greater time commitment up front, it was definitely worth it. The modification saved us time throughout the project and gave us the comfort of knowing that all of the links were rendered properly.
The most important thing when updating your application is choosing the approach that works best for your organization. While I would argue that rebuilding the framework from scratch is the ideal option, there are nonetheless situations in which a gradual migration is the better choice. It’s often not an undertaking that an organization can effectively staff while continuing to address new business needs. When completing your migration, it is important to have software engineers who understand the process and are able to quickly and efficiently address the challenges that your website brings. If you’re ready to make a change – Grio is ready to help!
For alternate methods of migrating AngularJS to React, check out my earlier blog post on Migrating from AngularJS to React.