Back to Blog
Development

Why We Moved to Next.js App Router (And What Broke Along the Way)

T
Tech Valley Corp
Aug 20, 2026 3 min read
Why We Moved to Next.js App Router (And What Broke Along the Way)

The old setup

Our site had been running on the Pages Router since it was first built. It worked fine — but every new feature felt like it was fighting the framework instead of working with it. Data fetching was scattered across getServerSideProps calls, and layouts had to be manually re-composed on every page.

When the App Router hit stability, we decided it was time.

What we were hoping to gain

  • Server Components by default — less client-side JavaScript shipped to the browser
  • Nested layouts — shared UI (navbars, sidebars) without prop-drilling or duplication
  • Colocated data fetching — no more prop chains five components deep

The migration, honestly

It took about three weeks for a mid-sized site. Here's what actually happened.

Week 1: The easy wins

Static marketing pages moved over almost for free. Server Components meant we could delete a surprising amount of useEffect + useState boilerplate that existed purely to fetch data on mount.

// Before: client-side fetch just to render static-ish content
useEffect(() => {
  fetch('/api/posts').then(res => res.json()).then(setPosts)
}, [])

// After: just... fetch it. It's a Server Component.
const posts = await getPosts()

Week 2: The friction

Not everything was smooth.

  1. Any component using useState, useEffect, or browser APIs needed 'use client' — easy to forget, and the errors weren't always obvious
  2. Third-party libraries that assumed a browser environment needed wrapping in dynamic imports with ssr: false
  3. Context providers had to move to the boundary between Server and Client Components, which meant restructuring a few layout files

"The mental model shift matters more than the syntax. Once 'this runs on the server unless I say otherwise' clicks, everything else follows."

Week 3: Performance tuning

MetricBeforeAfter
First Contentful Paint1.8s1.1s
JS shipped to client340kb210kb
Time to Interactive2.6s1.7s

The client-side JS reduction came almost entirely from moving data-fetching logic and static content out of Client Components.

What we'd tell past us

  • Migrate route by route, not all at once — running both routers side by side is fully supported and saved us from a big-bang rewrite
  • Audit every 'use client' boundary before moving on — it's easy to mark a whole tree client-side "just to be safe," which quietly kills the benefit
  • Budget real time for third-party library compatibility — this was the single biggest source of delays

Was it worth it?

Yes — but not because of any single dramatic win. It was the accumulation of smaller things: faster pages, simpler data fetching, fewer files fighting each other for state. If you're on the fence, migrating incrementally removes almost all the risk.

Found this useful? Share it:

T

Tech Valley Corp

Nikunj Kumar, CTO & COO

STAY IN THE LOOP

Get the latest tech insights, industry updates, and exclusive content delivered straight to your inbox.

Join 10,000+ subscribers

Expert 1
Expert 2
Expert 3
Expert 4
Expert 5
+10k