# What You’ll Learn#
This guide gives you a step-by-step Next.js App Router migration plan from the Pages Router, plus a practical checklist you can run through with your team.
You’ll cover routing, data fetching, SEO metadata, deployment considerations, and troubleshooting for frequent production issues like caching surprises, redirects, and dynamic routes.
If you need a refresher on fundamentals, start with Getting started with Next.js. If your team is new to the mental model behind server-first UI, read React Server Components guide. For SEO-specific context, see Why Next.js for SEO.
# When to Migrate vs. Stay Put#
App Router is the long-term direction of Next.js, but “latest” is not always “best for your next sprint.” Use this decision matrix to avoid half-migrations that stall mid-way.
| Signal | Migrate to App Router now | Stay on Pages Router for now |
|---|---|---|
| Product roadmap | You will add new sections, dashboards, or a new marketing site in the next 3 to 6 months | Only minor maintenance and bugfixing planned |
| Performance goals | You need faster TTFB, streaming, better caching control, or to reduce client JS | Current performance is already acceptable and stable |
| Data fetching | You want server-first fetching, parallel routes, and granular cache policies | You rely heavily on getServerSideProps patterns that are tightly coupled to page props |
| Team readiness | You can invest in code review guidelines and testing for server and client boundaries | Team is unfamiliar with RSC and cannot spare onboarding time |
| Infrastructure | You deploy on Vercel or a Node runtime that supports the features you need | You depend on a custom server setup or edge constraints that complicate parity |
| Risk tolerance | You can ship incrementally behind feature flags and measure | You have compliance or release constraints that make regressions expensive |
🎯 Key Takeaway: Migrate when you have a business reason and bandwidth to test and observe. App Router is powerful, but it punishes “ship and pray” migrations with caching and rendering surprises.
# Step-by-Step Migration Plan (Practical Checklist)#
Step 0: Baseline and Inventory#
Before you touch code, capture a baseline so you can prove the migration helped.
Checklist
- Record current Lighthouse scores for key templates: homepage, listing, detail page, checkout or signup.
- Track Core Web Vitals in real users if possible. Google reports that CWV correlates with bounce and conversion, and LCP improvements are often measurable after reducing client JS.
- Export your route list and map dependencies:
- Static routes
- Dynamic routes and catch-all
- Redirects and rewrites
- API routes
- Identify pages using:
getInitialPropsgetServerSidePropsgetStaticPropsand ISRnext/headnext/router
- Decide if you will do a “new routes in App Router first” approach or a strict page-by-page migration.
Deliverable: a migration spreadsheet that lists every route, its data strategy, and SEO requirements.
Step 1: Upgrade Next.js Safely#
Do not migrate routers and upgrade major versions at the same time if you are behind. First, upgrade to a modern stable Next.js version while staying on Pages Router, then migrate.
Checklist
- Update Next.js and React to supported versions for App Router.
- Remove deprecated config and check build warnings.
- Confirm CI builds and production deploy are stable.
- Ensure you can run
next buildwithout runtime-only errors.
⚠️ Warning: If you are still using
getInitialPropsin_app, you will likely carry legacy constraints into the migration. Plan to replace it early, or isolate it to the remaining Pages Router area.
Step 2: Create the App Router Shell#
Add the app directory while keeping pages working. This is the backbone of incremental adoption.
Checklist
- Create
app/layout.tsxas the global layout. - Create
app/page.tsxfor the root route if you want to migrate the homepage early, otherwise keep it inpages. - Add
app/not-found.tsxandapp/error.tsxfor consistent error UX. - Decide how you will handle global providers:
- Providers that must run in the browser belong in a client component wrapper.
- Server-only logic stays in server components.
Example layout skeleton:
// app/layout.tsx
export const metadata = {
title: "Your Site",
description: "Default description",
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}Keep your layout minimal at first. You can reintroduce providers after the first route works end-to-end.
Step 3: Routing Migration (Pages to App Router)#
App Router routing is file-system based, but conventions differ.
| Pages Router | App Router |
|---|---|
pages/index.tsx | app/page.tsx |
pages/about.tsx | app/about/page.tsx |
pages/blog/[slug].tsx | app/blog/[slug]/page.tsx |
pages/blog/[...slug].tsx | app/blog/[...slug]/page.tsx |
_app.tsx | app/layout.tsx plus optional client provider wrapper |
_document.tsx | handled by app/layout.tsx for most cases |
API routes in pages/api | keep as-is or move to app/api/route.ts |
Checklist
- Migrate routes one by one, starting with low-risk pages.
- For each route, confirm:
- URL stays identical
- redirects still apply
- canonical URLs remain correct
- analytics events still fire
💡 Tip: Start with a route that has minimal data and no auth. It gives you a known-good template for layout, metadata, and navigation.
Step 4: Navigation and Params#
In App Router:
next/routeris replaced bynext/navigation.- Route params are passed via the
paramsprop to page components. - Query string is accessed with
searchParams.
Example:
// app/products/[id]/page.tsx
export default function ProductPage({
params,
searchParams,
}: {
params: { id: string };
searchParams: Record<string, string | string[] | undefined>;
}) {
const id = params.id;
const ref = searchParams.ref;
return null;
}Checklist
- Replace
useRouterfromnext/routerwith:useRouter,usePathname,useSearchParamsfromnext/navigationin client components
- Avoid reading
window.locationin server components. - Keep navigation state in the URL when it impacts SEO or shareability.
Step 5: Data Fetching Migration#
This is where most time goes, and where most production bugs appear.
Replace getServerSideProps
Instead of returning props, fetch inside the server component. By default, fetch is cached in App Router unless you opt out.
// app/users/page.tsx
export default async function UsersPage() {
const res = await fetch("https://api.example.com/users", { cache: "no-store" });
const users = await res.json();
return null;
}Use cache: "no-store" for truly dynamic pages, similar to SSR. Use next: { revalidate: seconds } for ISR-like behavior.
Replace getStaticProps and ISR
// app/blog/page.tsx
export default async function BlogPage() {
const res = await fetch("https://api.example.com/posts", {
next: { revalidate: 300 },
});
const posts = await res.json();
return null;
}Replace getStaticPaths
Use generateStaticParams:
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const res = await fetch("https://api.example.com/slugs", {
next: { revalidate: 3600 },
});
const slugs: string[] = await res.json();
return slugs.map((slug) => ({ slug }));
}Checklist
- For every migrated route, explicitly decide caching:
cache: "no-store"for per-request datarevalidatefor ISR-style pages- default caching only when data is truly static and safe
- Ensure authenticated pages do not cache user-specific content.
- Use server components for data fetching by default, then introduce client components only when needed.
⚠️ Warning: The most common App Router production incident is accidentally caching personalized content. If a page reads cookies or auth state, treat it as dynamic and verify responses are not shared across users.
Step 6: Client vs. Server Components Boundaries#
A good migration reduces client JS, but you must control where client components exist.
Checklist
- Only add
"use client"when you need:- state
- effects
- browser-only APIs
- event handlers
- Keep data fetching in server components and pass data down.
- Avoid importing large UI libraries into server components if they end up forcing client boundaries.
A simple pattern for global providers:
// app/providers.tsx
"use client";
export default function Providers({ children }: { children: React.ReactNode }) {
return children;
}Then wrap it in app/layout.tsx where needed.
Step 7: SEO and Metadata Migration#
App Router uses the Metadata API, which is more structured than next/head and helps prevent duplicate tags.
| SEO concern | Pages Router approach | App Router approach |
|---|---|---|
| Title and meta description | next/head in page | export const metadata or generateMetadata |
| Dynamic metadata | computed in component | generateMetadata based on params |
| Canonical | manual tag | metadata alternates.canonical |
| Open Graph | manual meta tags | openGraph field |
| Robots | manual meta tag | robots field |
Example dynamic metadata:
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: { params: { slug: string } }) {
const res = await fetch(`https://api.example.com/posts/${params.slug}`, {
next: { revalidate: 600 },
});
const post = await res.json();
return {
title: post.title,
description: post.excerpt,
alternates: {
canonical: `https://example.com/blog/${params.slug}`,
},
};
}Checklist
- Recreate all critical tags:
- title
- meta description
- canonical
- robots rules for noindex pages
- Open Graph and Twitter cards
- Confirm pagination and filters use canonical correctly.
- Ensure structured data remains present if you use JSON-LD.
- Validate with Google Rich Results Test and Search Console URL Inspection.
For deeper reasoning on why this matters, see Why Next.js for SEO.
ℹ️ Note:
generateMetadatacan fetch data. Treat it as part of your route’s data strategy and apply the same caching rules, otherwise you can end up with stale titles while the page content updates.
Step 8: Redirects, Rewrites, and Middleware#
Most redirects can remain in next.config.js, but migrations often introduce path changes and trailing slash differences.
Checklist
- Export and test your redirect rules in staging.
- Validate old URLs still resolve correctly:
- marketing campaigns
- backlinks
- indexed URLs
- If you use Middleware:
- verify it does not force dynamic behavior on otherwise static routes
- avoid heavy computation in Middleware
Example redirect snippet:
// next.config.js
module.exports = {
async redirects() {
return [
{ source: "/old-blog/:slug", destination: "/blog/:slug", permanent: true },
];
},
};Step 9: API Routes and Server Actions Considerations#
You can keep pages/api while migrating UI routes. Move to app/api only when it adds value.
Checklist
- Keep stable API contracts during the UI migration.
- If you introduce server actions, confirm:
- CSRF considerations
- auth checks are server-side
- caching does not hide mutation results
If your team is still learning RSC and server-first patterns, align on conventions first. The fastest way is to use a short internal doc and link to React Server Components guide.
Step 10: Deployment and Observability#
App Router changes runtime behavior, especially around caching. You need visibility.
Checklist
- Ensure your hosting supports required runtimes and streaming behavior.
- In staging, test:
- cold start performance
- cache hit ratios if applicable
- error pages and 404 behavior
- Add logging around:
- fetch failures
- unexpected
notFoundcases - auth edge cases
- Monitor:
- TTFB distribution, not just averages
- error rate
- crawl errors in Search Console after launch
💡 Tip: Run a controlled rollout: migrate a single route group, deploy, and watch metrics for 24 to 48 hours before expanding. App Router issues are often traffic-dependent and won’t show in local testing.
# Practical Migration Checklist (Copy and Use)#
Use this as your “definition of done” for each migrated route.
| Area | Check | Done criteria |
|---|---|---|
| Routing | URL parity | Route path matches old route and deep links work |
| Routing | Dynamic params | params and searchParams are correct and typed |
| Data fetching | Caching policy | Explicit no-store or revalidate chosen where needed |
| Data fetching | Error handling | notFound, error.tsx, and API failure states covered |
| RSC boundary | Client components | Only components needing browser APIs use "use client" |
| SEO | Metadata parity | Title, description, canonical, OG tags match old behavior |
| SEO | Indexing rules | robots rules preserved for private or thin pages |
| Redirects | Old URLs | All legacy URLs redirect or serve equivalent content |
| Performance | JS payload | Client bundle does not grow unexpectedly |
| Deployment | Environment parity | Env vars present, runtime compatible, build passes |
| QA | Analytics | Page views and key events still fire correctly |
| QA | Regression tests | At least smoke tests for top user flows |
# Troubleshooting: Common Pitfalls and Fixes#
Caching Surprises#
Symptoms
- Users see stale content after publishing.
- One user sees another user’s personalized data.
- A page updates only after a redeploy.
Root causes
- Default fetch caching used unintentionally.
- Shared cache on authenticated routes.
- Metadata cached differently than page content.
Fix checklist
- 1For user-specific pages, use
cache: "no-store"and avoid caching derived data. - 2If content should update every N seconds, set
next: { revalidate: N }on all relevant fetches. - 3Ensure
generateMetadatauses the same caching strategy as the page. - 4Audit components that read cookies or headers and confirm the route is treated as dynamic.
Redirects and Canonicals Breaking SEO#
Symptoms
- Search Console shows duplicate URLs or “alternate page with proper canonical tag.”
- Organic traffic dips after migration.
- Campaign URLs land on unexpected pages.
Root causes
- Missing canonical tags after removing
next/head. - Trailing slash changes.
- Redirect rules not copied exactly.
Fix checklist
- 1Recreate canonical URLs using metadata
alternates.canonical. - 2Verify trailing slash behavior matches previous production.
- 3Diff old and new redirect lists and run automated tests on top 100 URLs.
- 4Validate HTTP status codes. Use 301 for permanent, 302 for temporary.
Dynamic Routes Not Matching or Returning 404#
Symptoms
- Dynamic pages work locally but 404 in production.
- Catch-all routes behave differently.
generateStaticParamsbuilds but some paths are missing.
Root causes
- Route file moved but folder structure is wrong.
generateStaticParamsnot returning all needed paths.- Mixed usage of
pagesandappcreates ambiguous routing expectations.
Fix checklist
- 1Confirm folder structure:
app/segment/[param]/page.tsx. - 2Ensure
generateStaticParamsreturns objects with correct keys. - 3If some paths are truly dynamic, rely on runtime rendering and set caching accordingly.
- 4Add monitoring for unexpected 404 spikes post-release.
Client Component Bloat and Performance Regression#
Symptoms
- Lighthouse JS execution time increases.
- Large bundles after migration.
- Hydration warnings.
Root causes
- Too many
"use client"files. - Importing a client-only dependency into a shared component that forces client rendering.
- Moving global providers into client scope unnecessarily.
Fix checklist
- 1Make server components the default. Add
"use client"only at leaf nodes. - 2Split interactive widgets into isolated client components.
- 3Use dynamic imports for heavy client-only UI.
- 4Compare bundle sizes before and after and track changes per route.
Deployment Differences Between Staging and Production#
Symptoms
- Works in preview, fails in production.
- Edge runtime differences.
- Build passes locally but fails in CI.
Root causes
- Missing env vars.
- Different Node runtime versions.
- Hosting platform cache rules differ from local expectations.
Fix checklist
- 1Pin Node version in CI and match production runtime.
- 2Ensure env vars are present for both build and runtime where needed.
- 3Run
next buildin CI with the same flags as production. - 4Test cold deploy behavior, not only hot reload.
# Key Takeaways#
- Migrate incrementally by running Pages Router and App Router side-by-side, route by route, with clear “done” criteria.
- Treat caching as a first-class migration task: explicitly choose
no-storeorrevalidatefor every route and metadata fetch. - Rebuild SEO using the Metadata API, including canonicals, robots rules, and Open Graph, then validate in Search Console.
- Replace
next/routerwithnext/navigation, and rely onparamsandsearchParamsinstead of manual URL parsing. - Expect most issues in production around caching, redirects, and dynamic routes, and prepare automated URL tests plus monitoring.
# Conclusion#
A successful Next.js App Router migration is less about moving files and more about rethinking data fetching, caching, and SEO as deliberate decisions per route.
If you want Samioda to review your migration plan, audit caching and SEO parity, or handle an incremental rollout with minimal risk, contact us and we’ll help you ship the App Router upgrade with measurable performance and stability gains.
FAQ
More in Web Development
All →Tailwind CSS Best Practices: Building Maintainable UIs (Production Patterns for 2026)
A production-focused guide to tailwind CSS best practices: component patterns, custom config, dark mode, responsive strategy, and copy‑pasteable examples for maintainable UIs.
Web Application Security Checklist for 2026 (Next.js + OWASP Top 10)
A practical web application security checklist for 2026, focused on Next.js: OWASP Top 10 coverage, authentication, input validation, CSP, HTTPS, and production-ready headers.
React Server Components (RSC): What They Are and How to Use Them in Next.js App Router
A practical 2026 guide to React Server Components: what they are, why they matter, and how to use server vs client components correctly in Next.js App Router with copy-pasteable examples.
Need help with your project?
We build custom solutions using the technologies discussed in this article. Senior team, fixed prices.
Related Articles
Why Next.js Is the Best Framework for SEO in 2026
Learn why Next.js dominates SEO performance in 2026. Server-side rendering, Core Web Vitals, structured data, and real performance comparisons.
React Server Components (RSC): What They Are and How to Use Them in Next.js App Router
A practical 2026 guide to React Server Components: what they are, why they matter, and how to use server vs client components correctly in Next.js App Router with copy-pasteable examples.
Progressive Web Apps (PWA): Complete Guide for 2026
A practical progressive web app PWA guide for 2026: concepts, business benefits vs native apps, and a step-by-step Next.js implementation with manifest and service worker code.