grEEff.dev
ServicesWorkProcessPricingInsights
Start Your Project
Engineering

March 10, 2025

14 min read

Modernizing Legacy React Apps: The Migration to Server Components

Moving from Create React App to Next.js App Router isn't just a refactor—it's a paradigm shift. Here is our playbook.

Pio Greeff

Pio Greeff

Founder & Lead Developer

Deep dive article

The "Legacy" Problem

It feels strange to call React "Legacy," but here we are. Between 2016 and 2022, the industry standard for building web apps was Create React App (CRA).

  • Client-side rendering (CSR).
  • Webpack.
  • Data fetching in useEffect.
  • Global state libraries like Redux for everything.

There are tens of thousands of these apps in the wild. They power massive SaaS platforms. And they are starting to show their age.

  • Performance: Massive JS bundles sent to the client. Slow "Time to Interactive."
  • SEO: Without server-side rendering, Google struggles to index them efficiently.
  • Waterfalls: Components triggering API calls, which trigger child components, which trigger more API calls. Loading spinner hell.

The industry has moved to React Server Components (RSC) via the Next.js App Router. This isn't just an upgrade; it is a fundamental architectural shift. Migrating is necessary, but it is hard.

The Mental Shift

The biggest barrier to migration isn't syntax; it's the mental model.

The Old World (Client-Side) Thinking in "Time": When does this run?

  • "Component mounts -> Trigger useEffect -> Update State -> Re-render." The browser does all the work.

The New World (Server-Side) Thinking in "Place": Where does this run?

  • Server Component: Fetches data directly from the DB. Render HTML. Send to client.
  • Client Component: The interactive "islands" (buttons, forms) that hydrate on the browser.
  • Data flows down. The client component receives data as props; it doesn't fetch it itself (usually).

Our Migration Playbook: The Strangler Fig Pattern

We never recommend a "Big Bang" rewrite (stopping feature dev to rewrite the whole app). It notoriously leads to failed projects. Instead, we use the Strangler Fig Pattern. We slowly wrap the old app in the new architecture until the old app disappears.

Step 1: The Monorepo Wrapper

We create a Next.js App Router project alongside the existing CRA app. We use Next.js Rewrites to serve the old app on a catch-all route (/[...slug]).

  • New routes are built in Next.js.
  • Old routes hit the Legacy App. This allows us to ship something new on Day 1 without breaking the old site.

Step 2: Extracting the Shell

We migrate the "Shell"—the Sidebar, Header, and Footer—to Next.js layouts. The "Page Content" is still the legacy app, loaded via an iframe or a shared component mount. Now the user gets the speed benefit of a static shell, even if the inner content is still checking useEffect.

Step 3: Route-by-Route Migration

We pick a high-value page. Let's say, the "Dashboard Landing." We rewrite it using Server Components.

  • Remove useEffect: We delete client-side fetching hooks.
  • Async/Await: We fetch data directly in the component body.
  • Remove State: We stop storing data in Redux unnecessarily. We just pass it as props.

Example Code Change:

Legacy (Client)

Modern (Server)

Step 4: Isolate Client Interactivity

We push the "client" logic down to the leaves of the tree. The "Submit Button," the "Chart," and the "Drag and Drop" zone become 'use client' components. The parent page remains a Server Component. This drastically reduces the JavaScript bundle size.

Benefits of Migration

Why go through this pain?

  1. 60% Less JavaScript: By keeping dependencies (like date-fns or internal logic) on the server, we don't send them to the user's browser.
  2. Instant First Paint: The user sees content immediately, not a white screen waiting for JS to download.
  3. Simplified DX: No more managing isLoading states for every single data fetch. The code is linear and easier to read.
  4. SEO Dominance: Server-rendered content is pure gold for search engines.

Conclusion

The future of React is Server-First. The migration is inevitable. If you have a large legacy codebase and don't know where to start, looking into our Services might be the first step. We have effectively migrated enterprise platforms without downtime.

Don't let tech debt slow you down. Modernize incrementally.

Found this useful?

Share it with your network