Follow Us

CodeWithSabir

  • Contact Us
  • Privacy Policy
  • About
  • Terms & Conditions

All Rights Reserved © 2026

  • Light
  • Dark
Next.js

Next.js App Router vs Pages Router: Which Should You Use?

Sabir Soft
Sabir Lkhaloufi
  • February 5, 2026
  • 4 min read

Next.js App Router vs Pages Router: Which Should You Use?

The App Router was introduced in Next.js 13 and became stable in 14. In 2026, it's the default for new projects. But understanding what actually changed — and when the Pages Router is still acceptable — helps you make better architectural decisions.

What Actually Changed

The App Router isn't just a new file structure. It's a fundamentally different rendering model based on React Server Components (RSC). The Pages Router renders everything as client-side React with optional server-side data fetching functions. The App Router treats server rendering as the default and client rendering as opt-in.

Pages RouterApp Router
Default component typeClient ComponentServer Component
Data fetchinggetServerSideProps, getStaticPropsasync component functions
LayoutsManual _app.tsxNested layout.tsx files
Loading statesManualloading.tsx convention
Error handling_error.tsxerror.tsx per segment
StreamingNot supportedBuilt-in with Suspense
API routespages/api/*app/api/*/route.ts

File Structure Comparison

# Pages Router
pages/
├── _app.tsx          # Global layout
├── _document.tsx     # HTML document
├── index.tsx         # / route
├── about.tsx         # /about route
├── blog/
│   ├── index.tsx     # /blog route
│   └── [slug].tsx    # /blog/:slug route
└── api/
    └── posts.ts      # /api/posts endpoint

# App Router
app/
├── layout.tsx        # Root layout (replaces _app + _document)
├── page.tsx          # / route
├── about/
│   └── page.tsx      # /about route
├── blog/
│   ├── layout.tsx    # Shared blog layout
│   ├── page.tsx      # /blog route
│   ├── loading.tsx   # Loading UI
│   └── [slug]/
│       └── page.tsx  # /blog/:slug route
└── api/
    └── posts/
        └── route.ts  # /api/posts endpoint

Data Fetching: The Biggest Difference

Pages Router

// pages/blog/[slug].tsx
export async function getServerSideProps({ params }) {
  const post = await getPostBySlug(params.slug)
  if (!post) return { notFound: true }
  return { props: { post } }
}
 
export default function BlogPost({ post }) {
  // post is always available — no loading state needed
  return <article>{post.title}</article>
}
// Static generation with Pages Router
export async function getStaticProps({ params }) {
  const post = await getPostBySlug(params.slug)
  return { props: { post }, revalidate: 3600 }
}
 
export async function getStaticPaths() {
  const slugs = await getAllPostSlugs()
  return { paths: slugs.map(slug => ({ params: { slug } })), fallback: 'blocking' }
}

App Router

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  return getAllPostSlugs().map(slug => ({ slug }))
}
 
// The component IS the data fetching — no separate function needed
export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug)
  if (!post) notFound()
 
  return <article>{post.title}</article>
}

The App Router eliminates the mental overhead of matching data fetching functions to components. The component fetches its own data and renders it — simpler to read and reason about.

Nested Layouts

This is where App Router has a genuine advantage for complex UIs:

// app/dashboard/layout.tsx — wraps all /dashboard/* routes
export default function DashboardLayout({ children }) {
  return (
    <div className="flex">
      <Sidebar />
      <main className="flex-1">{children}</main>
    </div>
  )
}
 
// app/dashboard/settings/layout.tsx — nested inside dashboard layout
export default function SettingsLayout({ children }) {
  return (
    <div>
      <SettingsTabs />
      {children}
    </div>
  )
}

In Pages Router, you'd implement this manually in each page or through complex _app.tsx logic. With App Router, the file system defines the layout hierarchy.

Route Handlers vs API Routes

// Pages Router: pages/api/posts.ts
export default function handler(req, res) {
  if (req.method === 'GET') {
    res.json({ posts: [] })
  }
}
 
// App Router: app/api/posts/route.ts
export async function GET(request: Request) {
  return Response.json({ posts: [] })
}
 
export async function POST(request: Request) {
  const body = await request.json()
  // ...
  return Response.json({ success: true }, { status: 201 })
}

Route Handlers use the web standard Request/Response APIs instead of Node.js req/res. They can also run on the Edge Runtime.

When to Use Pages Router

Despite App Router being the default, Pages Router is still appropriate when:

  1. You have a large existing codebase — migrating a 200-page Pages Router app to App Router is a significant project. Run both concurrently instead (pages/ and app/ can coexist).

  2. Your team is new to React Server Components — the RSC model requires a shift in mental model. For teams under tight deadlines, Pages Router might be safer short-term.

  3. Heavy third-party dependencies — some libraries still assume a browser DOM and don't work in Server Components. Context-heavy UI libraries are a common pain point.

When to Use App Router

For any new project started in 2026: use App Router. The reasons:

  • Server Components reduce JavaScript bundle size significantly
  • Built-in streaming prevents slow data from blocking the whole page
  • Nested layouts make complex UI hierarchies maintainable
  • The direction Next.js is investing in — Pages Router gets bug fixes, not new features

Migration Strategy

If you're on Pages Router and want to migrate incrementally:

  1. Keep pages/ intact — both routers can coexist
  2. Move layout logic to app/layout.tsx first
  3. Migrate one route at a time, starting with simple pages
  4. Move API routes to app/api/ as you touch them
  5. Remove pages/ once all routes are migrated
# Both directories work simultaneously
pages/
  legacy-page.tsx     # Still works
app/
  new-page/
    page.tsx          # New page works alongside legacy

Common Mistakes When Migrating

1. Adding 'use client' everywhere. This defeats the purpose of App Router. Only use it when you need hooks or browser APIs.

2. Forgetting async on data-fetching components. Server Components can be async — use it.

3. Nesting Client Components around Server Components. Client Components can't import Server Components. Pass them as children instead.

4. Expecting getServerSideProps patterns to work. App Router has no getServerSideProps. Data fetching happens in the component itself.

Key Takeaways

  • App Router is the right choice for all new Next.js projects in 2026
  • The key change is the rendering model: Server Components by default, Client Components opt-in
  • Nested layouts in App Router are cleaner and more maintainable than Pages Router approaches
  • Pages Router and App Router can coexist — migrate incrementally rather than all at once
  • Route Handlers use web standard Request/Response — more portable than Pages Router API routes
Popular Blogs
Claude AI vs ChatGPT: An Honest Comparison for Developers
  • April 28, 2026
AI Tools Every Developer Should Be Using in 2026
  • April 20, 2026
Using the Claude API in Real Projects: A Practical Developer Guide
  • April 15, 2026
Prompt Engineering for Developers: Write Prompts That Actually Work
  • April 10, 2026
Categories
AIDevOpsNext.jsMobile DevelopmentWeb Development

Related Posts

Next.js
How to Build a Fullstack App with Next.js 15: Complete Guide
Sabir Khaloufi·Feb 25, 2026
Next.js
Next.js Server Components Explained: What They Are and Why They Matter
Sabir Khaloufi·Feb 20, 2026
Next.js
Authentication in Next.js with NextAuth.js v5: The Complete Setup
Sabir Khaloufi·Feb 15, 2026