Migrating a large, open-source React application to Next.js and Vercel

If your company started building with React over 5 years ago, chances are you implemented your own custom solution or internal framework. Many engineers and teams want to explore technologies like Next.js and Vercel. However, some don't know where to get started because it's so far from their current reality or they don't see how supporting a custom framework is holding them back.

As a coding exercise, we wanted to show what this could look like by migrating a large, open-source React application to use Next.js.

We managed to remove 20,000+ lines of code and 30+ dependencies, all while improving the local iteration speed of making changes from 1.3s to 131ms. This post will share exactly how we incrementally adopted Next.js and Vercel to rebuild the BBC website.

The BBC React Application

The BBC website is a large, production-grade, open-source React application. It is a completely custom React SPA (single-page application). Very few projects of this size and scope are developed in the open. We admire and applaud the work the BBC has done. This isn't about pointing fingers or calling anyone out. You'll see they have done a good job of configuring their application.

This project is a perfect example of many large-scale React applications. You or your company could have a similar set up. For example, this application contains:

  • React

  • TypeScript

  • CSS-in-JS (Emotion)

  • Babel

  • Webpack

  • ESLint

  • Stylelint

  • Prettier

  • Cypress

  • Jest

  • Lighthouse checks

  • Web vitals monitoring

  • Optimizely (client-side experimentation)

  • Storybook

  • React Router

  • React Helmet

  • React Lazyload

  • And more...

This architecture is very common for companies and developers building React applications in the past 5 years. However, many of these integrations and features are included in Next.js with little to no configuration. Our goal is to show switching to Next.js and Vercel can provide a demonstrative impact to your site performance and developer experience.

Goals

Let’s use the BBC website as an example of such an application and see how we can approach incrementally moving to Next.js and Vercel.

The primary goals should be:

  • Reuse as much as possible. At first glance, there is a decent amount of code that can be reused. Emotion is a good styling library, so there's no need to remove or change that right now. The React components and the built-in component library are well done and seem completely reusable. These should be leveraged and not discarded.

  • Delete as much as possible. This seems contradictory to the previous statement, but it has a different purpose. We want to remove anything from the project that is no longer necessary due to Next.js's capabilities. Let's see how much we can get rid of to reduce the overall code that needs to be maintained so developers can focus on features and delivery.

  • Improve developer experience and performance. Custom solutions require creating and maintaining documentation or the passing of tribal knowledge to understand the codebase and become a contributor. Projects that leverage well-known and well-documented frameworks such as Next.js allow for quicker onboarding, easier contributions, quicker adoption of new standards (like React Suspense) without having to learn them and implement them into a custom solution, and more.

As a proof of concept, let's see if we can take two primary routes of the BBC website, convert them to use Next.js, and deploy them to Vercel.

  • /news

  • /news/articles/[slug]

Let’s walk through how we can approach tackling an update like this.

Getting Started

We can start by reading through the documentation and walking the codebase, making mental notes of things that may be useful to us later.

Then, let's fork the repository and make two clones:

  • One running the application in its current, untouched state for reference and debugging.

  • One to run our Next.js changes.

This will also give us the ability to compare before and after metrics when complete. The best first step to make is to start with the smallest, simplest change, to prove a working concept and get it all the way out to production.

We can take this small change from running locally and deploy it all the way out to Vercel. I’ve found establishing this pipeline up front in a project is easier than battling deployment issues later when you think your work is done. This will also give us Preview Deployments along the way, so we can see and share our progress with others.

For our first step, based on what we learned from the documentation and code, we decide to get Next.js running with Emotion by:

  • Installing Next.js.

  • Removing the existing Babel config to let Next.js control as much of the configuration as possible, adding configurations later if needed.

  • Adding a next.config.js and _app.js to enable Emotion.

  • Adding a simplistic /news page with minimal styles to prove the configuration works.

  • Adding the Vercel for GitHub integration to the forked repository.

This resulted in our first commit. There were some verification issues when building on Vercel, so we can disable ESLint verification during builds, at least for now.

This gives us our first preview with some simple hover styles to prove Emotion is working as expected.

Our first commit of incrementally adopting Next.js to rebuild the BBC website.
Our first commit of incrementally adopting Next.js to rebuild the BBC website.

With a basic foundation in place, let's see if we can try and get the front page /news to render.

Migrating the Front Page

The documentation pointed out some key pieces of information for getting started here.

Let’s start by changing the /news route we added in Next.js to use the FrontPage component and pass it the corresponding data. This seems logical as it has a pageData prop. We pass the data, but we’re getting errors.

The first set of errors is due to the app using import aliases to avoid using relative imports (ie. ../..), so we need to let Webpack know how to reconcile them via next.config.js.

The next set of errors is due to an uninitialized state. The website also makes extensive use of React hooks for state management. The documentation has a section on page render lifecycles that highlights why we’re getting errors. We need to add the PageWrapper component and a handful of context providers to _app.js . Doing so allows the page to render for the first time!

This is an important lesson to remember when transitioning apps… Passing data to components and wiring up state will cause your application to “light up like a Christmas tree”. You should focus on that very early on in the conversion.

However, the Next.js site was showing slightly different data than the unmodified clone we made earlier. The documentation explains how routes process data before passing it to page-level components. There is a route and getInitialData for each page. We locate the getInitialData for the /news page and apply the appropriate filter functions to the fetched data in Next.js and rendered data matches.

When Vercel tried to deploy these changes, it hit a new build error with timezone data from Moment that wasn’t reproducible during local development. To simulate a production deployment, we can run npm run build locally. This led to the discovery that our unmodified clone’s Webpack config is configured to create a tz folder with a smaller subset of timezone data to save on bundle size at build/run time. Once we add the same custom Webpack plugin to our next.config.js, Vercel builds and deploys without issue.

To complete this route, we can change the hardcoded /news page to be a dynamic page route so it can load any BBC service and change the “most read” data from being an additional fetch to being fetched with the initial page data to reach full page parity.

This gives us our second preview milestone!

Our application is starting to look more real.
Our application is starting to look more real.

We can now view any BBC service as a front page! A few examples:

One more route to go!

Adding the Article Page

With most of the state and context already populated and working knowledge of how the page routing, rendering, and initial data loading works, we can make quick work of the article page.

Let’s add a new dynamic route for articles, get initial data, filter it, and pass it to the ArticlePage component.

With this, we get our third preview url with minimal effort. It really was that easy. This reaffirms our belief above that zeroing in on data and state early on is key.

Our third iteration shows a dynamic route with Next.js, including handling data and state.
Our third iteration shows a dynamic route with Next.js, including handling data and state.

According to the documentation, there are only two article routes that work without internal BBC API access, so these are the only routes we can use to test.

Creating a Fake API

To better simulate data coming from a real API, let’s create a fake API to load page data, which simplifies the code in Next.js pages.

Note that we don't use Next.js's /pages/api convention here because we are using getStaticProps to pre-render the pages at build time, which means we cannot fetch data using the running app itself before the app is built. Ideally, the API would be it's own app, but this works for a proof of concept.

We also add statusCode to the initial page data, which allows us to properly render an error page since the base Next.js page route is fully dynamic.

Tidying Up

With both routes functional, we can fix minor console errors and Lighthouse issues, comparing the untouched cloned app to our Next.js app.

Some highlights include:

End result, we reach full parity.

Removing Unnecessary Code

Finally, it is time to see just how much code can be eliminated or replaced using Next.js.

All this while completely reusing all existing data schemas, state, components, and styles! Here are our final results:

Our final result – a fully migrated page from a custom React and Express.js server to Next.js and Vercel.
Our final result – a fully migrated page from a custom React and Express.js server to Next.js and Vercel.

Reviewing Before & After

Now it's time to compare our Next.js site with the existing BBC website.

Performance-wise, BBC engineers and contributors deserve kudos for delivering a highly performant and fine-tuned website. Their Lighthouse scores were already good. Our Next.js version scores were relatively even with existing BBCs scores, with a few scores doing marginally better or worse on both sides (if you view the score links, disregard the SEO score for the Next.js site; it’s because there isn’t a robots.txt and Vercel preview urls are not indexable by default).

However, moving to Next.js brought some notable developer and user experience benefits:

  • Average HMR Time improved from 1.3s to 131ms

    • Average of ten runs; adding and removing a <p> tag from FrontPage

  • The number of network requests was reduced from 57 to 34

    • Based on an incognito session, empty cache, and hard reload

Unrealized Future Potential

Even with all these improvements, it still feels like we're just getting started. There is still so much potential for other optimizations and enhancements we can make in future iterations.

Conclusion

If all things are nearly equal performance-wise, then what is the point?

We have managed to remove 20,000+ lines of code and 30+ dependencies!

And what did we have to do to achieve nearly the same results? Nothing.

We can't get back the time spent on learning and configuring these dependencies to create a custom framework - but we can cut down on future maintenance.

By leveraging Next.js, we can let go of being experts and maintainers of this frontend tooling and leverage the community surrounding Next.js. You can benefit from new features and enhancements offered through Next.js - often with a simple, non-breaking version bump. By leveraging Vercel Preview Deployments, we can verify our progress in isolation with the full confidence that the end result will scale to meet the BBC's traffic demands and reduce reliance on internal DevOps teams.

Next.js and Vercel enable you and your team to do your best work, work only you can do for your project or company, at the moment of inspiration. This freedom allows you to focus on your product and customers instead of application infrastructure.

We hope this has given you and your team an idea of how how to approach such a conversion of your own custom React setup. And BBC, if you’re interested in seeing this through, please reach out and let us know.