Currently viewing the human version
Switch to AI version

What Breaks First

Product search started returning 404s after migrating. Turns out next/router doesn't exist anymore. Vercel deleted the entire fucking package in Next.js 13.4.0. Not deprecated - gone.

// This worked fine yesterday
import { useRouter } from 'next/router'

const router = useRouter()
const { productId, category } = router.query // RIP

Now you need three different imports to do what one hook used to do:

'use client'
import { useRouter, useSearchParams, useParams } from 'next/navigation'

// useRouter() only pushes routes now
// useSearchParams() for ?category=shoes
// useParams() for /products/[id]

The router.isReady property? Gone. No replacement. Delete every router.isReady check because it doesn't exist anymore.

Middleware Stops Working (Zero Error Messages)

Spent 4 hours debugging why auth stopped working. No error messages. No console logs. Middleware just... stopped running.

Turns out you can't put middleware inside app/ anymore. Has to be in project root:

project/
├── app/
├── middleware.ts  ← Only works here, nowhere else
└── package.json

In Pages Router, middleware ran on everything by default. App Router middleware needs explicit path matching or it won't run at all:

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico).*),'
  ],
}

This broke our auth routes silently. Users could access protected pages without logging in. Production was fucked for 2 hours before I realized middleware wasn't running at all. Zero logs. Zero errors. Just silently failing.

Error Pages Are Now Error... Hierarchies?

Your simple 404.js file doesn't work anymore. App Router uses multiple error files:

  • error.tsx for component crashes
  • not-found.tsx for 404s
  • global-error.tsx for root layout errors

Server component errors happen during rendering and show generic production error pages. Error boundaries only catch client component errors. Learned this when our dashboard crashed and users saw "Application error: a client-side exception has occurred" instead of our custom error page.

Auth Libraries Had to Start Over

NextAuth.js v4 doesn't work with App Router. At all. v5 rewrote the entire API. Every authentication pattern we had needed rewriting.

Session handling split into multiple APIs. Server components use auth(), client components use useSession(), middleware gets sessions differently. Nothing from v4 works anymore.

Clerk, Auth0, and Supabase all have separate App Router guides now because their old patterns broke too.

Development Lies to You

Everything works in next dev, then production breaks. Development and production behave completely differently in App Router.

Our dashboard worked fine locally, then 404'd in production. Took 2 hours to realize it was trying to fetch from localhost:3000 during the build process. Dynamic routes get statically generated at build time unless you tell them not to.

Always run npm run build && npm start before deploying. The development server uses different rendering strategies than production. I learned this the hard way when our client's site went down on launch day. Took 3 hours to roll back and another 4 hours to figure out what the hell happened.

After Wasting a Weekend on App Router Migration

Here's what actually matters if you don't want to spend your entire weekend debugging this bullshit like I did.

Get the Router Imports Right

Don't try to fix all your router imports at once. Pick one component that breaks and learn the pattern. Took me 3 attempts to understand what replaces what:

// Your old code everywhere
const router = useRouter()
const { productId } = router.query
const category = router.query.category

The replacement isn't obvious:

'use client'
import { useSearchParams, useParams } from 'next/navigation'

const searchParams = useSearchParams()
const params = useParams()

const category = searchParams.get('category')    // For ?category=shoes
const productId = params.productId               // For /products/[productId]

Copy this pattern to every fucking component using router imports. There's no automated migration tool - each import needs manual replacement. I had to fix 47 components by hand over two days. My wrist still hurts.

The router.isReady check everywhere? Delete it. App Router doesn't need it and it doesn't exist anymore.

Fix Your Middleware Before You Deploy

Middleware breaks silently when moved from Pages Router. The file must be in your project root, not inside app/:

your-project/
├── app/
├── middleware.ts  ← Here, not anywhere else
└── package.json

Add this matcher config or middleware runs on every static file and absolutely fucks up your build:

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico).*),'
  ]
}

Test auth routes after moving the file. I deployed without testing and our entire auth system stopped working. Took 30 minutes to figure out why.

Dynamic Routes Need Folders Now

Dynamic routing changed from files to folders. Your pages/products/[id].js file becomes a nested folder structure:

app/
└── products/
    └── [id]/
        └── page.tsx

Every dynamic route needs its own folder with a page.tsx inside. Nested dynamic routes become nested folders. Parameters come through props instead of router.query:

export default function ProductPage({ params }: { params: { id: string } }) {
  return <div>Product {params.id}</div>
}

Actually Test Production Builds

Development and production behave differently in App Router. Always run this before deploying:

npm run build && npm start

If npm run build fails, production deployment fails. If routes work after npm start but fail after deploying, your hosting platform doesn't support some App Router feature.

Static generation can break routes that work in development. I pushed to production without testing locally and spent 3 hours debugging why routes that worked fine in dev were 404ing in prod. Client was not happy. Neither was I.

Production Issues You Won't See in Development

App Router development doesn't catch production issues. Development and production use completely different rendering strategies, so shit breaks after you deploy. Every fucking time.

Static Generation Fails in Stupid Ways

Pages work in development but fail during npm run build with fetch errors. App Router pre-renders everything at build time when possible.

API routes don't exist during the build process. This breaks routes that fetch from internal APIs:

// This works in dev, breaks in production build
export default async function HomePage() {
  const posts = await fetch('http://localhost:3000/api/posts')  // Build fails here
  return <div>Posts</div>
}

Call your database directly or force dynamic rendering:

export const dynamic = 'force-dynamic'  // Skip static generation

export default async function HomePage() {
  const posts = await fetch('http://localhost:3000/api/posts')  // Now it works
  return <div>Posts</div>
}

Spent 2 hours figuring out why our homepage wouldn't build. The fetch worked perfectly in development. Build process kept failing with "ECONNREFUSED" errors that made zero sense.

Error Pages Don't Work Like You Think

App Router error boundaries work differently than expected. Server component crashes show generic error pages in production, even with proper error.tsx files.

Client component errors trigger error boundaries. Server component errors don't trigger error.tsx files and show minimal information.

Create an error.tsx file in each route folder:

// app/dashboard/error.tsx
'use client'

export default function Error({ error, reset }: { error: Error, reset: () => void }) {
  console.error('Dashboard broke:', error)
  return (
    <div>
      <h2>Dashboard Error</h2>
      <button onClick={reset}>Try Again</button>
    </div>
  )
}

Test error boundaries by throwing test errors. Don't assume they work. Our error pages looked great in development, then showed "Application error: a client-side exception has occurred" in production.

The Build Process Lies

npm run build succeeds even with broken routes. It only fails for build-time errors, not runtime routing problems.

Always test the production server locally:

npm run build && npm start

If routes work after npm start but fail on Vercel/Netlify, your hosting platform doesn't support some App Router feature. Took me 4 deployments to figure this out.

Memory Usage Doubles

App Router uses more memory during builds than Pages Router. Build processes can run out of memory on smaller servers.

If build processes get killed without errors, increase memory allocation:

NODE_OPTIONS="--max-old-space-size=4096" npm run build

Our builds kept failing on the CI server with no error message. Build would just die. Took 3 tries to realize it was running out of memory. GitHub Actions default runners apparently can't handle App Router builds.

Caching Breaks in Production

Routes get cached aggressively in production while showing fresh data in development. Add dynamic rendering config to routes that need current data:

export const revalidate = 0  // Don't cache this route
export const dynamic = 'force-dynamic'  // Render at request time

Without these configs, production apps serve stale data from the cache for extended periods. Our admin dashboard showed data from 3 days ago in production while displaying live data in development.

Authentication Breaks Silently

NextAuth.js session handling changed completely for App Router. Server components get session data differently than client components:

// Server component
import { auth } from '@/lib/auth'

export default async function Dashboard() {
  const session = await auth()  // Server-side session
  return <div>Hello {session?.user?.name}</div>
}

// Client component
'use client'
import { useSession } from 'next-auth/react'

export default function ClientDashboard() {
  const { data: session } = useSession()  // Client-side session
  return <div>Hello {session?.user?.name}</div>
}

Test auth flows in production mode. Session cookies behave differently between development and production. Spent half a day figuring out why users kept getting logged out after deployment. Session cookies weren't persisting. Turns out NextAuth.js v5 changed how cookies work in production mode.

Frequently Asked Questions

Q

"Module not found: Can't resolve 'next/router'" - what the hell?

A

Vercel deleted the entire next/router package in Next.js 13.4.0. Not deprecated. Gone. Use next/navigation instead but it's not a drop-in replacement. Import `use

SearchParams()for query params,useParams()for route params like[id], and usePathname()` for the current path. Half the old router methods don't exist anymore.

Q

My middleware just stopped working. No error messages.

A

It's in the wrong place. Move middleware.ts to your project root, not inside the app/ folder. Also add a matcher config or middleware runs on every static file and breaks your build:

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)
}
Q

Links work in development but 404 after deployment.

A

Because development lies to you. Next.js tries to statically generate everything at build time in production. Run npm run build && npm start locally to see the real errors. Development and production are completely different beasts.

Q

Where's my 404.js file? App Router broke it.

A

Create app/not-found.tsx instead. Or call notFound() from server components to trigger 404s. The old 404.js pattern doesn't exist anymore.

Q

NextAuth.js v4 doesn't work anymore. Why?

A

Because they rewrote the entire API for App Router. Upgrade to Next

Auth.js v5 or your auth will break. Same for Clerk, Auth0, and every other auth library

  • they all have separate App Router docs now. Server components get session data differently than client components. Nothing from Pages Router auth patterns works anymore.
Q

My [id].js dynamic routes stopped matching.

A

App Router uses folders for dynamic routes, not files. Change pages/posts/[id].js to app/posts/[id]/page.tsx. Every dynamic route needs its own folder now.

Q

Error boundaries don't catch server component crashes.

A

That's correct. Server component errors happen during server rendering, before error boundaries exist. Create error.tsx files in route folders to catch these instead. Error boundaries only work for client component errors after hydration.

Q

My next.config.js rewrites stopped working.

A

App Router's file-based routing interferes with rewrites. Move redirect logic to middleware or use the redirect() function in server components instead of next.config.js rewrites.

Q

Can I run Pages Router and App Router together?

A

Technically yes, but don't. You'll have duplicate auth systems, confusing navigation, and maintenance hell. Use hybrid mode only for gradual migration, then pick one router and stick with it.

Q

getServerSideProps is gone. What replaces it?

A

Server components. Make your component async and fetch data directly:

export default async function Page() {
  const data = await fetchData()  // Just fetch it
  return <div>{data}</div>
}

No getServerSideProps wrapper needed.

Q

Query parameters vanish after navigation.

A

Use useSearchParams() instead of router.query. Query params don't persist automatically in App Router

  • you have to pass them explicitly through navigation.
Q

API routes in /app/api aren't working.

A

The file naming changed. Instead of api/users.js with a default export, use app/api/users/route.ts with named exports:

export async function GET(request) {
  // Handle GET requests
}

export async function POST(request) {
  // Handle POST requests
}
Q

No loading states during navigation.

A

Create loading.tsx files in route folders or use useTransition with the router. App Router's loading system is more granular but requires explicit loading UI files.

Q

Middleware runs on every request, even images.

A

Add a matcher config to exclude static files, or middleware will run on every PNG and CSS file:

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)
}
Q

404 pages return 200 status codes in production.

A

This is intentional. App Router uses streaming responses that show 200 status codes even for 404s. It breaks SEO tools and monitoring but that's how it works.

Q

Can I roll back to Pages Router easily?

A

Hell no. App Router changes everything

  • file structure, data fetching, error handling, routing. Plan for weeks of work to roll back. Better to migrate incrementally and test thoroughly. I've seen teams spend a month rolling back.
Q

Environment variables work locally but not in production.

A

App Router's static generation happens at build time with different environment variables than runtime. Make sure your deployment platform provides the right variables during both build and runtime phases.

Q

My _document.js and _app.js files disappeared.

A

App Router uses app/layout.tsx instead of both _app.js and _document.js. The root layout replaces both with a different API for document structure and global providers.

Documentation (Read This, Then Google Your Actual Problems)

Related Tools & Recommendations

howto
Recommended

Migrating CRA Tests from Jest to Vitest

competes with Create React App

Create React App
/howto/migrate-cra-to-vite-nextjs-remix/testing-migration-guide
100%
howto
Recommended

Migrate from Webpack to Vite Without Breaking Everything

Your webpack dev server is probably slower than your browser startup

Webpack
/howto/migrate-webpack-to-vite/complete-migration-guide
95%
compare
Recommended

Which Static Site Generator Won't Make You Hate Your Life

Just use fucking Astro. Next.js if you actually need server shit. Gatsby is dead - seriously, stop asking.

Astro
/compare/astro/nextjs/gatsby/static-generation-performance-benchmark
79%
tool
Recommended

SvelteKit Authentication Troubleshooting - Fix Session Persistence, Race Conditions, and Production Failures

Debug auth that works locally but breaks in production, plus the shit nobody tells you about cookies and SSR

SvelteKit
/tool/sveltekit/authentication-troubleshooting
64%
integration
Recommended

SvelteKit + TypeScript + Tailwind: What I Learned Building 3 Production Apps

The stack that actually doesn't make you want to throw your laptop out the window

Svelte
/integration/svelte-sveltekit-tailwind-typescript/full-stack-architecture-guide
64%
tool
Recommended

Remix - HTML Forms That Don't Suck

Finally, a React framework that remembers HTML exists

Remix
/tool/remix/overview
57%
tool
Recommended

React Router v7 Production Disasters I've Fixed So You Don't Have To

My React Router v7 migration broke production for 6 hours and cost us maybe 50k in lost sales

Remix
/tool/remix/production-troubleshooting
57%
pricing
Recommended

Should You Use TypeScript? Here's What It Actually Costs

TypeScript devs cost 30% more, builds take forever, and your junior devs will hate you for 3 months. But here's exactly when the math works in your favor.

TypeScript
/pricing/typescript-vs-javascript-development-costs/development-cost-analysis
56%
integration
Recommended

Supabase + Next.js + Stripe: How to Actually Make This Work

The least broken way to handle auth and payments (until it isn't)

Supabase
/integration/supabase-nextjs-stripe-authentication/customer-auth-payment-flow
54%
howto
Recommended

Converting Angular to React: What Actually Happens When You Migrate

Based on 3 failed attempts and 1 that worked

Angular
/howto/convert-angular-app-react/complete-migration-guide
54%
alternatives
Recommended

Webpack is Slow as Hell - Here Are the Tools That Actually Work

Tired of waiting 30+ seconds for hot reload? These build tools cut Webpack's bloated compile times down to milliseconds

Webpack
/alternatives/webpack/modern-performance-alternatives
52%
tool
Recommended

Webpack Performance Optimization - Fix Slow Builds and Giant Bundles

built on Webpack

Webpack
/tool/webpack/performance-optimization
52%
tool
Recommended

Actually Migrating Away From Gatsby in 2025

Real costs, timelines, and gotchas from someone who survived the process

Gatsby
/tool/gatsby/migration-strategy
52%
tool
Recommended

Why My Gatsby Site Takes 47 Minutes to Build

And why you shouldn't start new projects with it in 2025

Gatsby
/tool/gatsby/overview
52%
tool
Recommended

Qwik - The Framework That Ships Almost No JavaScript

Skip hydration hell, get instant interactivity

Qwik
/tool/qwik/overview
50%
tool
Recommended

Deploy Qwik Apps to Production - Complete Guide

Real-world deployment strategies, scaling patterns, and the gotchas nobody tells you

Qwik
/tool/qwik/production-deployment
50%
integration
Recommended

Vite + React 19 + TypeScript + ESLint 9: Actually Fast Development (When It Works)

Skip the 30-second Webpack wait times - This setup boots in about a second

Vite
/integration/vite-react-typescript-eslint/integration-overview
48%
integration
Recommended

Stripe Terminal React Native Production Integration Guide

Don't Let Beta Software Ruin Your Weekend: A Reality Check for Card Reader Integration

Stripe Terminal
/integration/stripe-terminal-react-native/production-deployment-guide
46%
review
Recommended

Which JavaScript Runtime Won't Make You Hate Your Life

Two years of runtime fuckery later, here's the truth nobody tells you

Bun
/review/bun-nodejs-deno-comparison/production-readiness-assessment
44%
integration
Recommended

Build Trading Bots That Actually Work - IB API Integration That Won't Ruin Your Weekend

TWS Socket API vs REST API - Which One Won't Break at 3AM

Interactive Brokers API
/integration/interactive-brokers-nodejs/overview
44%

Recommendations combine user behavior, content similarity, research intelligence, and SEO optimization