When does a React to Next.js migration make sense?
Migrate when pages that should bring in search traffic are missing from Google, when first load is slow on mobile, or when shared links show blank previews. Those three symptoms share one cause: the browser receives an empty HTML shell and has to download and run your JavaScript before anything appears.
You can check this yourself in a minute. Open one of your public pages, right-click and choose “View page source”. If the source shows a near-empty body with a single root div and script tags, your content is built entirely in the browser. Now paste the URL into a WhatsApp chat: if the preview shows a generic title or none at all, link-preview bots see the same empty shell.
A React to Next.js migration is not always the answer. If your app is a private dashboard behind a login, with no page that needs to rank or be shared, the benefits are smaller and a tuned Vite build may do. The strongest cases are apps where public and private pages live together: marketplaces, listing portals, SaaS products with feature pages, and content sites that were built as SPAs years ago.
- Public pages missing or marked “Crawled – currently not indexed” in Search Console
- Largest Contentful Paint on mobile far above 2.5 seconds
- Blank or wrong link previews on WhatsApp, LinkedIn or X
- A Create React App project that no longer receives updates
Why does a client-side React app struggle with SEO and first load?
Because search engines and browsers both have to run your JavaScript before they see any content. For people, that means waiting; for crawlers, it means an extra step that some of them skip.
Google's Search Central documentation describes three phases for JavaScript pages: crawling, rendering in a headless Chromium, and indexing. Pages wait in a rendering queue, which the documentation says can take a few seconds or longer. It also states that server-side rendering or pre-rendering is still a great idea because it is faster for users and crawlers and not all bots can run JavaScript. Many AI crawlers and social preview bots fall into that last group.
First load suffers for a related reason. The Next.js migration guides for Create React App and Vite both point out that a purely client-side app must download and run the whole bundle before it can even start requesting data, and that fetching in useEffect creates waterfalls where each component waits for its parent. On a budget Android phone over mobile data, that chain is what users feel as a white screen.
A React to Next.js migration addresses both by sending HTML that already contains the content, then hydrating it. Our React developer page covers the cases where staying client-side is fine.
Create React App is deprecated: what does that mean for your app?
It means your build tool will not get new features or fixes, so the longer you wait, the more outdated dependencies pile up. On 14 February 2025 the React team announced it was deprecating Create React App for new apps.
In that announcement the team recommends moving to a framework such as Next.js, React Router or Expo, and lists Vite, Parcel and Rsbuild as build tools for cases where a framework does not fit. It names the problems it wanted to solve: no built-in routing, no integrated data fetching (leading to network waterfalls), manual code splitting and no server-side rendering.
Your CRA app will not stop working overnight. The risk is gradual: security advisories on old transitive dependencies, React upgrades that become harder, and developers who are less willing to work on the setup. If your app is also public-facing, a React to Next.js migration fixes the tooling and the SEO issue at once. If it is purely internal, moving to Vite may be the cheaper modernisation.
Teams already on Vite have the same SEO limitation if they use the default client-only React setup; the Next.js documentation has a separate Vite migration guide for exactly that case.
App Router or Pages Router: which should your migration target?
Target the App Router for almost every new React to Next.js migration. It is where Next.js development is focused, it supports React Server Components, streaming and nested layouts, and the official CRA and Vite migration guides use it.
The Pages Router still works and is simpler to learn for developers used to classic React. The Next.js docs say both routers can coexist in one project, so larger codebases sometimes move in two hops. There is a cost to mixing them: navigating between a page served by one router and a page served by the other is a full page load, and link prefetching does not cross between them.
Choose the App Router when
You are starting the migration now, want server components to cut client JavaScript, need nested layouts for dashboards, or plan to fetch data on the server.
Consider the Pages Router when
A key library only supports it, your team knows it well and needs a short migration, or an existing Next.js Pages app is being extended.
What changes in code
Data fetching moves from getServerSideProps and getStaticProps to async server components; getStaticPaths becomes generateStaticParams; useRouter comes from next/navigation; API routes become Route Handlers.
Planning SSR, SSG and ISR route by route
Every route in your app gets one rendering decision before migration starts. That plan decides the SEO outcome, the hosting bill and the order of work.
The rule we use: if a page must rank and its content is the same for everyone, build it statically or cache it; if it must rank but changes often, cache it with a revalidation time; if it depends on who is logged in, render it on the server or keep it as a client component behind auth. The route-planning table below maps common page types to strategies.
Put numbers on it early. How many product or listing pages exist today, and how often do they change? That decides whether generateStaticParams can prebuild them all or whether most should render on demand and then be cached. It also shapes the timeline, because public page groups are usually migrated first: that is where the traffic gain is.
This planning step is where a React to Next.js migration succeeds or fails. A migration that simply wraps the old SPA in Next.js and stops there changes the tooling but not what Google sees. The JAMstack page explains static-first thinking in more depth if most of your site is content.
Step one: run your existing React app inside Next.js
Start by getting the current app running inside Next.js unchanged, as a single-page app. The official Next.js guides for Create React App and Vite both recommend this first step, because it reduces risk and merge conflicts.
In practice that means installing Next.js, adding a root layout that replaces your old index.html, and creating an optional catch-all route (a folder named [[...slug]]) that loads your existing App component on the client with server rendering switched off. The guides set output to “export” at this stage, producing a static SPA much like the old build.
A few mechanical changes happen here. Environment variables exposed to the browser change prefix: REACT_APP_ for Create React App and VITE_ for Vite both become NEXT_PUBLIC_. Static image imports return an object in Next.js rather than a URL string, so img tags use the object's src property. CRA's proxy setting is replaced with rewrites in next.config. Next.js now uses Turbopack for local development by default, with webpack still available for projects that need a custom config.
At the end of this step nothing has visibly improved, but the app builds and deploys on Next.js, the old tooling can be removed, and every later step becomes a small, reviewable change.
Replacing React Router with Next.js file-based routes
Move routes out of React Router one group at a time, starting with public pages. Each group becomes folders in the app directory with a page file, and is removed from the catch-all as it moves.
A typical order: marketing and landing pages, then listing and detail pages, then blog or help content, then the logged-in area last. For each page, the old component is usually moved into a client component first, because it uses hooks and browser APIs. The new page file, a server component, fetches data and passes it down as props. Over time, parts that do not need interactivity are pulled back into server components to cut the JavaScript sent to the browser.
Routing hooks change. useNavigate, useParams and useLocation from React Router map to useRouter, useParams, usePathname and useSearchParams from next/navigation, which work in client components only. Links become next/link. Nested layouts replace wrapper components that used to render an Outlet.
We keep the catch-all SPA route alive until the last route is moved, so any page not yet migrated still works exactly as before. That is what lets feature work continue during a React to Next.js migration.
How do you keep URLs and rankings during the migration?
Keep every public URL identical wherever possible, and add a permanent redirect for any that must change. Before touching code, we crawl the live app and export every URL it exposes, along with titles and Search Console click data.
Three URL problems come up often in React to Next.js migrations. Hash routes (/#/products/42) are invisible to Google as separate pages; its documentation advises the History API instead of fragments. Moving to real paths is an SEO gain, but links shared in the past still carry the hash, so a small client-side script on the home page forwards them to the new paths. Trailing slashes may differ between your old host and Next.js; pick one form, configure it, and redirect the other. Renamed or merged pages need entries in the redirects list in next.config.
Soft 404s are the quiet problem. An SPA often returns a 200 status with a “not found” message, which Google treats as a soft 404. In Next.js, calling notFound() returns a real 404 status, which cleans up your index. After launch we test a sample of old URLs, resubmit the sitemap and watch coverage in Search Console. If a past migration already cost you traffic, see traffic drop after website migration.
Moving data fetching from useEffect to the server
Move data fetching for public pages into server components, so the HTML already contains the data when it reaches the browser. This is where most of the first-load improvement comes from.
In a typical SPA, a product page mounts, shows a spinner, calls the API in useEffect, then renders. After migration, the page file awaits the same API on the server and renders the finished markup. The Next.js migration guide explains the equivalents: a fetch cached by default behaves like the old getStaticProps; a fetch with no-store behaves like getServerSideProps; a revalidate time gives incremental static regeneration.
Client-side libraries such as React Query or SWR still have a place for data that changes while the user is on the page, like notifications or live stock. A common pattern is to fetch the initial data on the server and let the client library take over for updates.
Watch for code that assumes a browser. Anything that reads window, document or localStorage at import time will fail on the server. Those parts stay in client components, or are loaded with dynamic imports that skip server rendering. Hydration mismatch warnings (server and client rendering different output, often from dates or random IDs) are fixed as they appear.
Moving authentication without logging everyone out
Move tokens from localStorage to secure, HTTP-only cookies and check them on the server before a private page renders. The server cannot read localStorage, so an SPA's usual auth pattern has to change for server rendering to work.
The Next.js documentation describes Proxy (the convention formerly called middleware) as a way to run code before a request completes, for instance redirecting to the login page before an authenticated-only page loads. That removes the flash of private content, or the flash of a login screen, that many SPAs show while checking a token.
We plan a transition so existing users stay signed in: during a short overlap the app accepts the old token, issues the new cookie, and then drops the old path. If you use a hosted auth provider, we check its Next.js support and follow its recommended setup; if auth is custom, the cookie flags, expiry, refresh logic and CSRF protection are reviewed as part of the migration.
Role checks move to the server as well, which is more secure than hiding buttons in the browser. Pages that show data for one user render on the server with that user's session, and their responses are marked so no CDN caches them.
API routes, backend calls and hosting after migration
Keep your existing backend where it is unless there is a clear gain in moving it. Next.js can call it from server components, or proxy it through rewrites so browser code keeps using the same paths.
Small endpoints that only serve the web app (form handlers, webhook receivers, API proxies that hide a key) often move into Route Handlers, the App Router's replacement for pages/api. Heavier logic, and any API shared with Android and iOS apps, usually stays a separate service built in Node, NestJS or another stack; our Node.js hiring guide covers that side.
Hosting changes with the rendering plan. A static export can sit on any CDN, but the Next.js guides note that output: “export” gives up server features such as SSR and API routes. Once routes render on the server, you need a platform that runs Next.js, such as Vercel, another serverless host with Next.js support, or a Node server or container you manage. We open the account in your name and set caching headers so static and cached pages are served from the edge.
- Mostly static after migration: CDN or static host, lowest cost
- Some server-rendered routes: serverless platform with Next.js support
- Heavy server work or data residency needs: Node server or containers in your chosen region
SEO checklist for a React to Next.js migration
Treat SEO as a migration deliverable, not an afterthought. The framework makes good SEO possible; the checklist makes sure it happens on every route.
- Crawl of the old app saved, with URLs, titles, descriptions and canonicals
- Unique title and description per route through the Metadata API or generateMetadata
- Canonical tags and one consistent trailing-slash policy
- Real 404 status for missing items via notFound()
- 301 redirects for every changed or hash-based URL
- XML sitemap and robots file generated by the app
- Structured data (Organisation, Product, Article, FAQ where genuine)
- Open Graph images and titles so WhatsApp and LinkedIn previews work
- Core Web Vitals checked on a mid-range Android phone
- Search Console coverage and performance watched for several weeks after launch
For ongoing technical work after the move, see our technical SEO freelancer service. Nobody can guarantee rankings; what a React to Next.js migration removes is a technical barrier.
How much does a React to Next.js migration cost?
With us, a small public-facing React app (a few templates, no logins) starts at ₹10,000; an app with logins, roles, dashboards and API work starts at ₹60,000 as custom software. If the migration also adds hundreds of new SEO pages generated from data, that part starts at ₹20,000.
What drives the figure is predictable once the route plan exists. Each route type needs its own rendering decision and testing. Auth changes take care because they touch every private page. Browser-only libraries (rich text editors, chart libraries, maps) may need client-only wrappers. Custom webpack or Babel configuration, service workers and unusual build steps each add work. The number of individual pages matters much less than the number of distinct templates.
Across the market, quotes for a React to Next.js migration vary widely. When you compare them, check that each includes the route plan, URL crawl and redirects, auth migration, a staged release and post-launch monitoring; a low quote that leaves these out is quoting a different job. Our web app cost guide explains how app features are priced.
How long does a React to Next.js migration take?
A small public-facing app can move in one to two weeks; an app with logins, dashboards and APIs typically takes six to twelve weeks, delivered in stages so you see improvements long before the end.
The first days go on the audit: reading the code, crawling the live URLs, listing libraries and building the route plan. The first stage, running the old app inside Next.js as an SPA, usually follows within the first week or two. Public page groups are converted next, one group per release, each tested on staging and then deployed. Auth and the logged-in area come after, then the clean-up that removes the catch-all route and the old tooling.
What slows migrations down is rarely Next.js itself. It is untested legacy code that hides surprises, libraries that assume a browser, and unclear ownership of decisions. A named decision-maker on your side and access to a staging copy of your API save more time than anything we can do in code. The phase table below gives a typical breakdown.
React to Next.js migration for teams across India
We run migrations remotely for businesses anywhere in India: code access through your Git host, calls on Google Meet or WhatsApp in English or Hindi, payments by UPI or bank transfer. Location does not change the price.
Different cities bring different apps. Property and listing portals in Hyderabad and Noida need thousands of listing pages indexed. SaaS teams in Bengaluru and Chennai want feature and pricing pages that rank while the product stays a React app. Ed-tech and coaching platforms in Jaipur and Indore want course pages crawlable. D2C brands in Ahmedabad and Surat built early storefronts as React SPAs and now want product pages that search engines can read, and travel businesses in Kochi want itinerary pages indexed without losing their booking flow.
International teams can work with us in USD; see hiring developers in India for how overlap hours work.
Worked example and red flags: a property portal moving off Vite
Here is a hypothetical case. Say a property portal in Hyderabad runs a React app built with Vite and React Router: a home page, locality pages, around 3,000 listing pages, a blog, and a logged-in area where agents post listings. Search Console shows most listing pages as discovered but not indexed, and mobile LCP is poor.
The route plan would make locality and blog pages static, with regeneration when content changes; listing pages rendered on demand and cached with a short revalidation window, returning a real 404 once a listing is removed; and the agent dashboard as client components behind a cookie session checked by Proxy. Stage one wraps the Vite app in Next.js. Stage two moves locality and listing pages, with the Metadata API writing titles like “2 BHK in Gachibowli” from data and structured data for each listing. Stage three moves the blog. Stage four migrates auth and the dashboard. Old hash links from early marketing campaigns are forwarded to real paths. This would be quoted as custom software from ₹60,000.
Red flags to watch in any proposal: a plan to rewrite everything before shipping anything; no crawl or redirect list; auth left in localStorage with server rendering bolted on; hosting on the developer's account; and no Search Console monitoring after launch.