Everything about our migration from ReactJS to NextJS

The overall thought process, challenges, and outcomes: Peerlist's tech stack migration from ReactJS to NextJS.

Everything about our migration from ReactJS to NextJS

Hello folks 👋,

We announced our tech migration two months back in our November Release. Since then, I have been planning to write this blog but thought to take some time to have better clarity on everything. As things are pretty stable now (but we are still improving), I thought of sharing the whole thought process and how we took our decisions.

Spoiler Alert! This article covers most of our thoughts behind the decision and the process and might not go deep in tech, but follow along; in the end, it will be worth a read!

React is one of the most popular javascript libraries and is widely used in many applications these days, and NextJS is a framework built on ReactJS. You don’t understand the true power of Next until you start using it, and I am saying this from my own experience!

I have been working with React for more than 3 years, and I have always loved how it works. So when we started building Peerlist from scratch, React was my very obvious choice. Because of the MVP stage, we thought of working with our strengths (of course, React for the frontend) and building the first working prototype in ReactJS.

Initially, this worked; we could ship within our timelines, and everything was working smoothly. Soon, we realized the decision to go with plain React didn’t turn out to be good for us. We knew that this tech stack wouldn't scale with the product roadmap we have in place.

Why?

All technologies and frameworks are remarkable, but they are created to fulfill different use-cases. So when I said plain React wasn’t good for us, I was thinking of the following use-cases,

We needed a more SEO-friendly framework.

React is pretty good at creating Single Page Applications, but Google crawlers find it difficult to index and fully process the Javascript of your app. This will start affecting your SEO. For the websites like Peerlist, the user’s content is the Hero.

We wanted your Peerlist profile should be in the top 5 search results when someone is looking for you or a professional with similar skills as you. We had to crack the Google search algorithm to display your Peerlist profile.

We all know that SEO takes a reasonable amount of time to build, and we lost our initial couple of months by not getting indexed and ranked enough by Google. This became a deal-breaker!

Server-side rendering support.

We had two particular use-cases that needed our app to support server-side rendering (SSR). One of them was SEO which I already mentioned above, and the second was the custom social previews. Something like this -

Peerlist's social preview when you share your profile.

For the sites like Peerlist, where our focus is on highlighting users’ data, we needed the social preview of every user profile’s link customized for that user. If I want to share my Peerlist profile link, my info should get highlighted than the platform. You must have seen custom social previews for sites like GitHub, DEV, and Hashnode.

Both of these features needed SSR, and we did not find a good, robust, and scalable solution that could fit our requirement of turning a react app into SSR.

Routing

React apps heavily rely on React-Routers. Though React Router is a great library to handle routing, we started facing difficulty in maintaining and following conditional routing using it. Though React Router would have worked with a more refined implementation, we started looking for something easier to maintain, implement and scale.

Javascript Ecosystem

In our earlier implementation, our backend was developed using Springboot and Postgresql. This was an excellent combination, and we hardly faced any difficulty with this. Though this was the case, we decided to move entirely into the javascript ecosystem. It was more for the ease of development and leveraging the advantages of a single project structure and MongoDB.

But then, what next? NEXT.

With all these use-cases in our minds, we figured Nextjs was our ideal fit. Next is an opinionated framework that provides out-of-the-box support for SEO, SSR, routing, and API routes to create serverless APIs. We wanted everything and the addition of performance benefits.

Notably, these are the benefits of Next which we considered while choosing it

  1. SEO and image optimizations.
  2. Optimized bundling and code splitting to improve the app performance.
  3. Very intuitive and dynamic page routing.
  4. API routes to support serverless APIs.
  5. Built-in server-side-rendering support.
  6. A framework built with React

Migration process and challenges we faced

We started understanding the downsides of our earlier implementation, but the question was, when is the good time to migrate?

To set you a little context, we launched a closed beta of our app two months back and were in the process of shipping new features, testing them, and collecting more and more user feedback. So, we had to decide between shipping new features vs. migration.

Because of a small engineering team (🧑‍💻 x2), doing both in parallel was not the option. But taking up the migration means we have to pause feature development. Still, we decided to go ahead with migration because the earlier, the better!

Considering the earlier Reactjs project, frontend migration was a little easier. Most of the previous components were reusable. The only difference we considered was in the following four things.

  1. Moving from React Router to native Next router
  2. Adding SSR for certain pages
  3. Changing the folder structure as per Next
  4. Creating custom head components for meta tags to improve SEO

From this, frontend migration seemed pretty straightforward. What needed to do was to write the backend from scratch. As I mentioned, our earlier backend was in Springboot and Postgresql; moving it to javascript APIs with MongoDB meant writing and structuring everything from scratch.

Considering our timelines and resources for this migration, we decided to replicate everything as is without modifying it because we wanted to do it as quickly as possible and keep improving it later. But again, who controls that developer’s urge to refactor the code and implementation?

But on a positive note, this migration improved the implementation approaches. We made our system more refined and stable. Though these improvements made us miss the migration deadline, the overall improvements we experienced in our system were worth those efforts.

If I need to summarize the whole migration process and write down the learnings, here are those -

  1. Initially, I felt we should have given more thought to selecting the correct tech stack in the first attempt. But always remember, your first attempt will never be a polished and perfect product (that’s why it is called a prototype!). You are already testing your idea, so it's okay if you play with your strength and select the tech stack you are most comfortable with.
  2. No system can ever be made perfect! We all have seen bugs in the well-known apps and crashes happening with applications that we consider ideal, so you creating something with your best effort is all that is needed. Bugs will be part of your software as features are; the point is always in improving your system and minimizing them, not making a perfect system.
  3. Code refactoring and improvisations are good, but time-boxing them is essential. Otherwise, we fall into a rabbit hole.

That’s all I wanted to share from our migration process. I have deliberately tried to keep this article less technical and more of a thought process we went through to make it a little relevant. Do let me know in the comments or reach out to me on Twitter if you want to understand any particular part of the process. I will surely try to cover it in my next article.

Till then, keep exploring! ✌️