10 min read
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.
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.
There are routes and corresponding entry components for each part of the app.
Access to a real API is only available to internal BBC team members. Otherwise, local development is done using captured data responses saved as JSON files, which are included in the repository.
Lastly, a slight adjustment to our original goals... The
/news
route is actually dynamic! This didn’t become clear until reviewing the saved data responses. It's a base route that serves localized translations and content based on the "service" specified at this route (i.e./news
,/arabic
,/japanese
, etc.).
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!
We can now view any BBC service as a front page! A few examples:
And more...
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.
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.
https://simorgh-preview-3.vercel.app/news/articles/c6v11qzyv8po
https://simorgh-preview-3.vercel.app/persian/articles/c4vlle3q337o
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:
Prefixing all environment variables used on the client side with
NEXT_PUBLIC_
and then moving all the environment variables to Vercel for better management and security.Adding a custom
_document.js
to handle dynamicdir
andlang
HTML attributes.
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.
Completely remove Babel config and all related dependencies.
Completely remove
react-helmet
in favor ofnext/head
and subsequently update all<script>
tags to usenext/script
.Completely remove
react-router
and all related dependencies.Completely remove
express
and all related dependencies.Completely remove
fetch
dependencies in favor of Next.js's built-in fetch polyfill.Completely remove all custom app, route, data, and server handlers via Next.js's custom
_app.js
and file-system based routing.Keep our minimal Webpack config via
next.config.js
as mentioned above and remove all other Webpack dependencies.
All this while completely reusing all existing data schemas, state, components, and styles! Here are our final results:
https://simorgh-nextjs.vercel.app/news (and any other service)
https://simorgh-nextjs.vercel.app/news/articles/c6v11qzyv8po
https://simorgh-nextjs.vercel.app/persian/articles/c4vlle3q337o
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 fromFrontPage
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.
Remove the custom ESLint config and move to Next.js's integrated ESLint capabilities.
Change all links to use
next/link
for fast SPA-like page transitions.Change all
<img>
tags to usenext/image
and remove the custom placeholder component and thereact-lazyload
dependency.BBC supports AMP on its front page and article pages, which could be replaced with
next/amp
.Investigate Internationalized Routing to better handle translations, language, and language direction.
Create the best strategy for SSR, CSR, and ISR based on BBC's CMS, API, and business rules and goals.
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.