The App Router's release generated a lot of confusion because it changed more than routing conventions — it changed the default rendering model itself. Understanding that distinction is what actually clarifies the migration decisions, more than memorizing the new file-naming conventions.
Table of Contents
- The Real Change: Server Components by Default
- Routing Convention Changes
- Layouts and Nested Routing
- Data Fetching: What's Different
- Loading and Error States as First-Class Files
- Should You Migrate an Existing Pages Router App
- Common Mistakes
- FAQs
- Key Takeaways
The Real Change: Server Components by Default
In the Pages Router, every component was a Client Component by default — even if it rendered no interactivity, its JavaScript shipped to the browser. The App Router flips this: every component is a Server Component by default, rendered on the server with zero JavaScript sent to the client unless you explicitly opt into a Client Component with "use client".
This is the change that actually matters for performance and bundle size — a page that's mostly static content (a blog post, a marketing page) can now ship dramatically less JavaScript than the equivalent Pages Router implementation, because only the genuinely interactive pieces (a like button, a form) need to be Client Components.
// app/blog/[slug]/page.tsx — Server Component by default export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) { const { slug } = await params; const post = await getPost(slug); // runs on the server, no client JS for this return <article>{post.content}</article>; }
// components/like-button.tsx — explicitly a Client Component "use client"; export function LikeButton() { const [liked, setLiked] = useState(false); return <button onClick={() => setLiked(!liked)}>{liked ? "Liked" : "Like"}</button>; }
Routing Convention Changes
File-based routing moved from pages/ (where the file itself was the page) to app/ (where a folder represents a route segment, and page.tsx within it is the actual page component). This enables colocating related files (layout.tsx, loading.tsx, error.tsx) with the route they belong to, rather than maintaining a separate _app.tsx and custom error handling detached from individual routes.
app/ blog/ page.tsx # /blog [slug]/ page.tsx # /blog/:slug loading.tsx # shown while page.tsx's data fetching resolves error.tsx # shown if page.tsx throws
Layouts and Nested Routing
layout.tsx files nest automatically based on folder structure and persist across navigation within their scope — a layout.tsx in app/blog/ wraps every route under /blog/* without needing to be re-declared, and critically, it doesn't re-render on navigation between child routes, preserving state like scroll position or open UI panels that the Pages Router's _app.tsx approach couldn't do as cleanly.
Data Fetching: What's Different
The Pages Router required getServerSideProps or getStaticProps — special exported functions with specific signatures. The App Router's Server Components fetch data with plain async/await directly in the component, using the same fetch API you'd use anywhere, with Next.js providing extended caching semantics on top:
export default async function Page() { const data = await fetch("https://api.example.com/data", { cache: "force-cache", // or "no-store", or { next: { revalidate: 60 } } }).then((res) => res.json()); return <div>{data.title}</div>; }
This removes the special-function ceremony entirely — data fetching is just how you'd write any async function, which is a genuine simplification once you're used to it, though it's a real adjustment coming from the Pages Router's more explicit contract.
Loading and Error States as First-Class Files
loading.tsx automatically wraps the route in a Suspense boundary and displays while the page's data fetching resolves — no manual loading state management required for the basic case. error.tsx similarly wraps the route in an error boundary. Both are conventions the Pages Router had no direct equivalent for without manual implementation.
Should You Migrate an Existing Pages Router App
Next.js supports incremental migration — app/ and pages/ can coexist, letting you migrate route by route rather than requiring a big-bang rewrite. For a stable, working Pages Router app with no pressing performance issues, migration isn't urgent — the Pages Router isn't deprecated and continues to receive support. Migration makes the most sense when you're actively adding new routes (build them in app/ directly) or hitting specific performance issues (large client bundles) that Server Components would directly address.
Common Mistakes
- Marking everything
"use client"out of habit from Pages Router thinking, defeating the actual benefit of Server Components by default. - Fetching data in a Client Component when it could be a Server Component, unnecessarily shipping fetch logic and often an extra client-server round trip to the browser.
- Not understanding the caching implications of
fetch'scacheoption, leading to either accidentally stale data or accidentally uncached requests hitting an API harder than intended. - Attempting a full rewrite instead of incremental migration for a large existing Pages Router app, creating unnecessary risk and a long feature-freeze.
FAQs
Is the Pages Router deprecated? No — it remains supported, though the App Router is where new framework features are developed going forward. There's no forced migration deadline.
Can I use Server Components with Redux or other client-side state libraries? Client-side state management still works normally within Client Components; Server Components themselves don't participate in client-side state since they don't run in the browser at all.
Does the App Router support getStaticPaths equivalent functionality?
Yes, via generateStaticParams, which serves the equivalent role for static generation of dynamic routes.
How does the App Router affect SEO?
Generally positively — smaller client bundles and Server Component rendering can improve Core Web Vitals, and the Metadata API (see my Next.js Metadata API guide) provides cleaner per-route SEO metadata than the Pages Router's next/head approach.
Should new Next.js projects always use the App Router? Yes, for new projects — it's where framework development and community focus is concentrated, and Server Components' default performance benefits are valuable from the start.
Key Takeaways
- The App Router's core change is Server Components by default, not just new file conventions — this is what actually affects bundle size and performance.
- Data fetching is now plain async/await in Server Components, replacing the special-function contract of
getServerSideProps/getStaticProps. - Migration can be incremental (
app/andpages/coexisting) — a full rewrite is rarely necessary or advisable.
Related Reading
- Next.js Metadata API: A Practical Guide to Technical SEO
- Next.js Server Actions: When to Use Them and When Not To
- 10 Next.js Performance Tips for Production Apps
Next Steps
Migrating to the App Router or architecting a new Next.js project? Let's talk through the right approach for your specific app.