Going Article Route
The next target, /news/articles/[slug], is the article page itself. This route introduces a different set of concerns than the front page: it depends on server-side data fetching with query parameters. The good news is that the BBC codebase already has a getStaticPaths/getStaticProps pattern in the same areas where it defines its routes, so there’s baked-in guidance for the exact signatures Next.js expects.
There is also a critical prerequisite: the [slug] parameter is not the only input. The article’s physical path and its amp variant are both used to construct final URLs. In plain Next.js, you handle this with a custom next.config.js entry that maps the incoming URL to the right page component.
After enabling the dynamic route, the first render errors will be familiar from the /news migration: state not initialized, and hooks being called outside the expected provider tree. The route’s own documentation lists the page’s top-level providers and data transformers—all living in the app’s src/app area.
Wiring data from getInitialData into the component props fixes most of this. But one error is unique to the article route: an unhandled environment check that breaks during prerendering. In the original app, this logic sits inside a webpack DefinePlugin configuration. The same check fails in Next.js because the variable is only defined on the server. Using Next.js’s built-in env replacement for the identifier keeps the rest of the runtime code unchanged (the full list of supported env variable substitutions is in the Next.js docs).
With that environment issue solved, the article pages render with full data parity against the reference server. You can exercise both the canonical and AMP variants live at /news/articles/[slug].
Deleting What You No Longer Need
With both routes running on Next.js, the bigger win is removal. The BBC app’s architecture has these responsibilities that Next.js now handles natively:
Routing — React Router is gone because the app now uses the file-system router.
Assets and favicons — Next.js’s
next/headreplaces React Helmet for document head management and also takes over asset preloading; the BBC had a custom component for that.Code splitting — The BBC’s manual chunks and dynamic import boundaries are now handled automatically by Next.js’s compiler and its built-in script loader, removing three npm packages plus config code.
Performance monitoring — The custom web-vitals wrapper gets superseded by
next/scriptand the built-in Next.js analytics integration that reports field data (the same “web vitals” terminology, now part of the framework).
Styling, however, stays with Emotion. The app’s CSS-in-JS setup is mature and works well with Next.js’s React Server Components; the @emotion/react integration is enabled through the compiler options in next.config.js**, not by replacing any of the runtime CSS logic.
On the data-fetching side, the original app made many manual, per-component network requests. The pages migrated here initially did the same, but Next.js lets you hoist those fetch calls to the page level with cache tagging. You can delete components whose only purpose was to trigger those isolated fetches. The article page’s “most read” related fetches move into getStaticProps or an equivalent, reducing the total amount of code that ping-pongs client-server requests.
The most tangible result of this migration is what was deleted: 20,000+ lines of code and 30+ npm packages. A large part of that is because of the bundlers killing the custom Webpack env plugin, browser-list configuration, Babel presets for TypeScript and Emotion, plus three dozen devDependencies that are now unnecessary.
Developer Experience and Iteration Speed
Beyond raw deletions, local iteration speed improved dramatically. Fast Refresh in modern Next.js skips full reloads for many edits. For the BBC app, changing a line in the article route’s main component dropped rebuild time from 1.3 s to 131 ms, which you feel as near-instant feedback on a code change. This is on top of the tangible runtime cost savings from the new data-fetching approach: no client-side waterfall.
Longer term, onboarding new engineers becomes less about tribal knowledge of a hand-rolled framework and more about patterns that follow standard Next.js conventions. In the original project, understanding routing, data loading, and page lifecycle took reading internal docs or reading lots of code. That is replaced by Next.js’s conventions plus minimal, well-placed comments to explain any remaining product’s data transformations.
Still, the migration is not a monolithic rewrite—both the original and the migrated versions coexist. The strategy is cumulative: keep the existing app running, move one route at a time, deploy through preview URLs, and when the Next.js version of the route hits parity, it becomes the modern default. That’s how you adopt a new framework incrementally, not rebirth at once.
The two routes migrated here are only a slice of the BBC site’s long tail, but enough to prove that the larger goal is entirely plausible: a quarter-million-line application isn’t an obstacle to modern web infrastructure if you plan deletions and route conversions carefully.
A Dynamic Route for Articles
Once the existing state, context, and page infrastructure from the initial home page migration were in place, adding a dynamic article route required relatively little work. The new route fetches article data, filters it as needed, and passes it to the `ArticlePage` component. Because the data layer was already solved, this third preview was live with minimal effort—confirmation that resolving data and state concerns early is the highest-leverage part of this kind of migration.
One constraint worth noting: without internal API credentials, only two article routes can be tested against the actual BBC data. The working preview URLs are:
https://simorgh-preview-3.vercel.app/news/articles/c6v11qzyv8pohttps://simorgh-preview-3.vercel.app/persian/articles/c4vlle3q337o
Simulating the API and Handling Errors
To mimic how the full application would fetch data in production, the team created a lightweight fake API layer for loading page data. This simplified the code in the Next.js pages that were previously responsible for fetching and shaping that data themselves.
The fake API deliberately does not use Next.js's `/pages/api` convention. Because the pages rely on getStaticProps to pre-render at build time, the app cannot fetch data from its own running server before it has been built. A separate API would be the cleanest approach, but a local stand-in was sufficient as a proof of concept.
The initial page data model also gained a statusCode field. This addition enables proper error-page rendering, which is necessary because the main page route in Next.js is fully dynamic and would otherwise mask bad requests.
Closing the Parity Gap
Once both the home page and article routes worked, the focus shifted to eliminating console errors and resolving Lighthouse issues by comparing the untouched, cloned React application with the new Next.js version. Several fixes stood out as necessary for feature and behavior parity:
- Correcting public URL paths that were previously handled by the custom server.
- Prefixing all client-side environment variables with
NEXT_PUBLIC_and delegating their management to Vercel for better security and visibility. - Adding
togglesdata to the initial page data payload. - Creating a custom
_document.jsto control dynamicdirandlangHTML attributes.
After these adjustments, the Next.js application reached functional parity with the original custom React app.
What Could Be Deleted
Establishing parity made it clear how much of the original setup was replacing what Next.js provides out of the box. The cleanup removed substantial pieces of infrastructure without touching the underlying data schemas, state, components, or styles:
- Babel configuration and all direct dependencies
react-helmet, replaced bynext/head, with scripts moved tonext/scriptreact-router,express, and their related dependency trees- Custom
fetchdependencies, replaced by the URL polyfill built into Next.js - All custom app routes, data handlers, and server-handling logic Sup
A minimal Webpack config remains in next.config.js, but all other Webpack tooling was removed. The resulting vercel preview runs at:
https://simorgh-nextjs.vercel.app/newshttps://simorgh-nextjs.vercel.app/news/articles/c6v11qzyv8pohttps://simorgh-nextjs.vercel.app/persian/articles/c4vlle3q337o
Performance Before and After
Performance testing of the migrated /thai page showed the results were comparable to the live BBC site. The original Simorgh app was already well tuned, and in the comparison the two sides trade marginal wins on various Lighthouse metrics. (The Next.js SEO score in the automated reports should be ignored: Vercel preview URLs are not indexable by default, and the instance had no robots.txt.)
Where Next.js added measurable value was in developer experience and page weight:
- Average HMR time dropped from 1.3s to 131ms. This is the mean of ten runs, measured by adding and removing a paragraph tag in the home page component.
- Network requests dropped from 57 to 34. This is based on an incognito session with an empty cache and a hard reload.
Still on the Table
This migration was scoped as a proof of concept, and several improvements were left untouched as future work:
- Migrating from the project's custom ESLint configuration to Next.js's integrated linting
- Swapping anchors for
next/linkto enable SPA-like transitions - Replacing all
<img>tags withnext/image, which would remove the need for custom placeholder components and thereact-lazyloadlibrary - Evaluating
next/ampto supersede the existing AMP handling for front-page and article pages - Researching Internationalized Routing to improve how translation, language, and directionality are coordinated
- Defining a concrete split of SSR, CSR, and ISR based on the CMS and business rules
The Takeaway
Given near-equal page performance before and after, measuring the success of this exercise by Lighthouse alone would miss the point. The real outcome is that faithful rendering and routing are achievable after deleting 20,000+ lines of code and 30+ dependencies from the original render path—with no behavioral change to the application.
The time spent learning and maintaining a custom React setup such as this one is now converted into reliance on Next.js, which adds features and improvements through straightforward upgrades rather than requiring in-house expertise in bespoke tooling. Vercel preview deployments also decouple the team from internal DevOps cycles when verifying progress at scale.
For any frontend team running an HTTP/Server-rendered React stack that feels burdensome to maintain, the lesson from this conversion is fairly direct. Getting data and state modeling right first makes moving to Next.js an exercise in deleting code, not rewriting it.



