Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
menu
  1. Home
  2. /Web Development
  3. /Nextjs
  4. /App Router Vs Pages Router
Your Progress0%
0 of 70 completed

NextJS Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication

App Router vs Pages Router

Understanding Next.js routing systems and choosing the right one

Next.js has two routing systems: the Pages Router (legacy) and the App Router (modern). If you're learning Next.js now, you need to understand both—not because you'll use both, but because you'll encounter Pages Router code in existing projects and tutorials. This lesson will clarify the differences and confirm why we're focusing exclusively on the App Router in this series.

The Two Routing Systems

Pages Router (Legacy)

  • Released: Next.js 1.0 (2016)
  • Directory: pages/
  • Status: Still supported, not deprecated
  • Use case: Existing projects, legacy code
  • Rendering: Client components by default
  • Data fetching: getServerSideProps, getStaticProps

App Router (Modern)

  • Released: Next.js 13 (2022), stable in 13.4
  • Directory: app/
  • Status: Recommended for all new projects
  • Use case: New projects, modern features
  • Rendering: Server components by default
  • Data fetching: Direct async/await in components

Clear Recommendation

For any new project starting today, use the App Router. The Pages Router isn't going away, but the App Router is where all new features and improvements are focused. This entire tutorial series teaches the App Router exclusively.

Why Does Next.js Have Two Routing Systems?

Understanding the history helps explain why both exist:

The Journey

  1. 2016-2022: Next.js used only the Pages Router. It was revolutionary at the time and powered thousands of production applications.
  2. 2022: React announced Server Components—a new way to render React on the server with better performance.
  3. October 2022: Next.js 13 introduced the App Router as an experimental feature to support React Server Components.
  4. May 2023: Next.js 13.4 marked the App Router as stable and production-ready.
  5. Today: Both routers coexist. Pages Router remains fully supported for existing projects, while App Router is recommended for new ones.

🔄 Incremental Adoption

Next.js deliberately designed both routers to work side-by-side. This lets teams migrate from Pages Router to App Router incrementally, route by route, without rewriting their entire application at once.

Key Differences: Side-by-Side Comparison

1. File Structure and Routing

Pages Router

PLAINTEXT
pages/
  index.tsx           → /
  about.tsx           → /about
  blog/
    index.tsx         → /blog
    [slug].tsx        → /blog/:slug
  api/
    users.ts          → /api/users

Each file is a route. Simple and straightforward.

App Router

PLAINTEXT
app/
  page.tsx            → /
  about/
    page.tsx          → /about
  blog/
    page.tsx          → /blog
    [slug]/
      page.tsx        → /blog/:slug
  api/
    users/
      route.ts        → /api/users

Folders define routes, special files define behavior.

2. Component Types

Pages Router

All components are Client Components by default. Everything runs in the browser unless you use special data fetching functions.

TYPESCRIPT
// pages/index.tsx - Client Component
import { useState } from 'react';

export default function Home() {
  const [count, setCount] = useState(0);
  // This runs in the browser
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

App Router

All components are Server Components by default. They run on the server unless you add "use client".

TYPESCRIPT
// app/page.tsx - Server Component (default)
export default async function Home() {
  // This runs on the SERVER
  const data = await fetch('https://api.example.com/data');
  const posts = await data.json();
  
  return <div>{posts.map(post => ...)}</div>;
}

// To make it a Client Component:
"use client";  // Add this at the top
import { useState } from 'react';
export default function Home() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

Server vs Client Components - Quick Recap

Feature
Server Component
Client Component
Runs onServer (Node.js)Browser
Can use async/await✓ Yes✓ Yes
Can fetch data directly✓ YesVia API calls
Access to databases✓ Yes✗ No
Can use environment variablesAll variablesNEXT_PUBLIC_ only
Can use useState/useEffect✗ No✓ Yes
Can use event handlers✗ No✓ Yes
Can access browser APIs✗ No✓ Yes
Bundle size impactNo impactAdds to bundle
SEO friendly✓ YesDepends

3. Data Fetching

Pages Router - Special Functions

Use special functions that run at build time or request time:

pages/blog/[slug].tsx
import { GetServerSideProps } from 'next';

interface Post {
  title: string;
  content: string;
}

export default function BlogPost({ post }: { post: Post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

// This function runs on the server for each request
export const getServerSideProps: GetServerSideProps = async (context) => {
  const { slug } = context.params!;
  const res = await fetch(`https://api.example.com/posts/${slug}`);
  const post = await res.json();
  
  return {
    props: { post },
  };
};

App Router - Direct Async Components

Just use async/await directly in your Server Components:

app/blog/[slug]/page.tsx
// No imports needed! Just make the component async
export default async function BlogPost({
  params,
}: {
  params: { slug: string };
}) {
  // Fetch directly in the component!
  const res = await fetch(`https://api.example.com/posts/${params.slug}`);
  const post = await res.json();
  
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

Pages Router Data Flow

How data flows from server to client

1. Component Renders

Page component loads in the browser

1

2. getServerSideProps/getStaticProps

Special functions run on server (if defined)

2

3. Data Passed as Props

Data returned as props to component

3

4. Component Re-renders

Component renders with data on client

4

App Router Data Flow

How data flows from server to client

1. Server Component Executes

Component runs on server by default

1

2. Direct Data Fetching

Fetch data directly with async/await

2

3. HTML Generated

Server generates complete HTML

3

4. HTML Sent to Browser

Fully rendered content delivered instantly

4

⚡ App Router Advantage

Notice how much cleaner the App Router code is? No special functions to remember, no prop passing between data fetching and rendering. Just async/await where you need it!

4. Layouts and Shared UI

Pages Router - _app.tsx and _document.tsx

Use special files to wrap all pages:

pages/_app.tsx
import type { AppProps } from 'next/app';
import '../styles/globals.css';

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <nav>Navigation</nav>
      <Component {...pageProps} />
      <footer>Footer</footer>
    </>
  );
}

Problem: This wraps every page. You can't have different layouts for different sections easily.

App Router - Nested Layouts

Create layouts at any level:

app/layout.tsx
// Root layout (wraps everything)
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html>
      <body>
        <nav>Navigation</nav>
        {children}
        <footer>Footer</footer>
      </body>
    </html>
  );
}
app/dashboard/layout.tsx
// Dashboard-specific layout
export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="dashboard-wrapper">
      <aside>Dashboard Sidebar</aside>
      <main>{children}</main>
    </div>
  );
}

Advantage: Different sections can have completely different layouts!

5. Loading and Error States

Pages Router - Manual Implementation

pages/dashboard.tsx
import { useState, useEffect } from 'react';

export default function Dashboard() {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [data, setData] = useState(null);
  
  useEffect(() => {
    fetch('/api/dashboard')
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err);
        setLoading(false);
      });
  }, []);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return <div>{/* Render data */}</div>;
}

App Router - Automatic with Special Files

app/dashboard/loading.tsx
// Automatically shows while page loads
export default function Loading() {
  return <div>Loading dashboard...</div>;
}
app/dashboard/error.tsx
'use client';

// Automatically shows on error
export default function Error({ error, reset }) {
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}
app/dashboard/page.tsx
// Just focus on the happy path!
export default async function Dashboard() {
  const data = await fetch('/api/dashboard').then(r => r.json());
  return <div>{/* Render data */}</div>;
}

6. API Routes

Pages Router

pages/api/users.ts
import type { NextApiRequest, NextApiResponse } from 'next';

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  if (req.method === 'GET') {
    res.status(200).json({ users: [] });
  } else if (req.method === 'POST') {
    res.status(201).json({ user: req.body });
  }
}

App Router

app/api/users/route.ts
// Export named functions for each method
export async function GET() {
  return Response.json({ users: [] });
}

export async function POST(request: Request) {
  const body = await request.json();
  return Response.json(
    { user: body },
    { status: 201 }
  );
}

Both approaches work fine. The App Router version uses the Web Standards Request/Response API.

Complete Feature Comparison

FeaturePages RouterApp Router
Server Components❌ No✅ Yes (default)
Nested Layouts❌ Limited✅ Full support
Loading UIManualBuilt-in (loading.tsx)
Error BoundariesManualBuilt-in (error.tsx)
Data FetchingSpecial functionsAsync components
Streaming❌ No✅ Yes (Suspense)
Server Actions❌ No✅ Yes
Parallel Routes❌ No✅ Yes
Intercepting Routes❌ No✅ Yes
Learning CurveEasier initiallySteeper but worth it
Future SupportMaintained, no new featuresActive development
RecommendationExisting projects only✅ All new projects

Why the App Router is Better

Let's be clear about the advantages that make App Router the recommended choice:

1. Better Performance Out of the Box

  • Smaller Client Bundles: Server Components don't ship JavaScript to the browser
  • Faster Initial Load: HTML is generated on the server
  • Automatic Code Splitting: Only load what's needed for each route
  • Streaming: Send parts of the page as they're ready

2. Better Developer Experience

  • Simpler Data Fetching: No special functions, just async/await
  • Better File Organization: Group related files together
  • Built-in Features: Loading and error states without extra code
  • Flexible Layouts: Different layouts for different sections

3. Modern React Features

  • Server Components: Access to React's latest innovations
  • Server Actions: Handle mutations without API routes
  • Streaming: Progressive rendering with Suspense
  • Improved Caching: Better control over data freshness

4. Future-Proof

  • All new Next.js features target the App Router first
  • React team is focusing on Server Components
  • Community and ecosystem moving to App Router
  • Better prepared for future web standards

The Bottom Line

The App Router isn't just "new"—it's better. It provides better performance, better developer experience, and access to modern React features that aren't available in the Pages Router.

Can You Mix Both? Migration Strategy

Yes, you can use both routers in the same project! This is primarily useful for migration:

PLAINTEXT
my-next-app/
  app/               ← App Router (new routes)
    page.tsx         → /
    dashboard/
      page.tsx       → /dashboard
  pages/             ← Pages Router (old routes)
    api/
      legacy.ts      → /api/legacy
    old-page.tsx     → /old-page

Migration Path

  1. Create app directory: Add it alongside pages/
  2. Migrate incrementally: Move routes one by one to app/
  3. Test thoroughly: Ensure each migrated route works
  4. Remove pages directory: When all routes are migrated

Priority Rules

If the same route exists in both routers:

  • App Router takes priority for page routes
  • app/about/page.tsx overrides pages/about.tsx
  • API routes in pages/api still work even with app/ directory

🎯 For New Projects

Skip the Pages Router entirely! There's no reason to start with Pages Router in 2024. Begin with App Router from day one and enjoy all the modern features.

Common Questions

Is the Pages Router Being Deprecated?

No. The Pages Router is not deprecated and won't be removed. It's still fully supported and maintained. However, new features are being built for the App Router, so that's where the ecosystem is moving.

Will My Pages Router App Stop Working?

No. Existing Pages Router applications will continue to work indefinitely. Next.js is committed to supporting it for the long term.

Should I Migrate My Existing Pages Router App?

It depends:

  • If it's working fine: No rush to migrate
  • If you need new features: Consider migrating affected routes
  • If starting new sections: Use App Router for new code
  • If it's a greenfield project: Definitely use App Router

Why Are There Still Pages Router Tutorials?

Several reasons:

  • Many existing applications use Pages Router
  • Some tutorials haven't been updated yet
  • The Pages Router is still valid for certain use cases
  • Developers need to maintain legacy code

Will Learning App Router Help Me With Pages Router?

Yes! The concepts transfer. If you understand App Router, you'll understand Pages Router easily. The reverse is also true, but learning App Router first gives you access to more modern patterns.

Which Tutorials Should You Follow?

✅ Look for These Indicators (App Router)

  • Mentions "App Router" or "Next.js 13+"
  • Shows code in app/ directory
  • Uses page.tsx and layout.tsx
  • Talks about Server Components
  • Published after May 2023

⚠️ These Indicate Pages Router (Legacy)

  • Shows code in pages/ directory
  • Uses _app.tsx and _document.tsx
  • Mentions getServerSideProps or getStaticProps
  • Talks about "Next.js 12" or earlier versions
  • Published before 2023

📚 This Tutorial Series

Every lesson in this series teaches the App Router exclusively. You won't see any Pages Router code here. We're focused on teaching you the modern, recommended approach from the start.

Real-World Comparison: A Blog Post Page

Let's see how you'd build the same feature in both routers:

The Requirement

Display a blog post with data from an API, with loading and error states, in a custom layout.

Pages Router Implementation

pages/blog/[slug].tsx
import { GetServerSideProps } from 'next';
import { useState, useEffect } from 'react';
import BlogLayout from '@/components/BlogLayout';

interface Post {
  title: string;
  content: string;
  author: string;
}

export default function BlogPost({ slug }: { slug: string }) {
  const [post, setPost] = useState<Post | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    fetch(`/api/posts/${slug}`)
      .then(res => res.json())
      .then(data => {
        setPost(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err);
        setLoading(false);
      });
  }, [slug]);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!post) return <div>Post not found</div>;

  return (
    <BlogLayout>
      <article>
        <h1>{post.title}</h1>
        <p>By {post.author}</p>
        <div>{post.content}</div>
      </article>
    </BlogLayout>
  );
}

export const getServerSideProps: GetServerSideProps = async ({ params }) => {
  return {
    props: {
      slug: params?.slug,
    },
  };
};

App Router Implementation

Split across multiple files:

app/blog/layout.tsx
// Blog layout (wraps all blog pages)
export default function BlogLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <div className="blog-layout">
      <aside>Blog Sidebar</aside>
      <main>{children}</main>
    </div>
  );
}
app/blog/[slug]/loading.tsx
// Automatic loading UI
export default function Loading() {
  return <div>Loading post...</div>;
}
app/blog/[slug]/error.tsx
'use client';

// Automatic error UI
export default function Error({
  error,
}: {
  error: Error;
}) {
  return <div>Error: {error.message}</div>;
}
app/blog/[slug]/page.tsx
// The actual page - clean and focused!
interface Post {
  title: string;
  content: string;
  author: string;
}

export default async function BlogPost({
  params,
}: {
  params: { slug: string };
}) {
  // Direct data fetching
  const res = await fetch(`https://api.example.com/posts/${params.slug}`);
  
  if (!res.ok) {
    throw new Error('Failed to fetch post');
  }
  
  const post: Post = await res.json();

  return (
    <article>
      <h1>{post.title}</h1>
      <p>By {post.author}</p>
      <div>{post.content}</div>
    </article>
  );
}

Notice the differences:

  • App Router: Separation of concerns (layout, loading, error, page)
  • App Router: No useState or useEffect for data fetching
  • App Router: Cleaner, more focused page component
  • App Router: Automatic handling of loading and error states
  • Pages Router: Everything in one file, more boilerplate

Key Takeaways

  • Next.js has two routing systems: Pages Router (legacy) and App Router (modern)
  • App Router is recommended for all new projects
  • Pages Router is not deprecated but isn't getting new features
  • App Router uses Server Components by default, Pages Router uses Client Components
  • App Router has better performance, DX, and access to modern React features
  • You can use both routers during migration
  • This tutorial series focuses exclusively on the App Router
  • Learning App Router first makes understanding Pages Router easier
  • Look for "App Router" or "Next.js 13+" in tutorials to ensure you're learning the modern approach

What's Next?

Now that you understand why we're using the App Router, it's time to dive deep into file-based routing—the core concept that makes Next.js so powerful and intuitive.

In the next lesson, we'll explore how folders and files in the app directory automatically create routes, how to create static and dynamic routes, and how Next.js's routing conventions make building complex applications simple.

🎓 You're Learning the Right Way

By focusing on the App Router from the start, you're learning Next.js the modern way. You're building skills that will serve you for years to come, and you're positioned to take advantage of all the latest React and Next.js innovations!

Test Your Understanding

Question 1 of 4

Which routing system is recommended for new Next.js projects?

Understanding Next.js routing systems? This guide explains the difference and which to use!

Previous
Next.js Project Structure Explained
Next
File-Based Routing Basics

Continue Your Next.js Journey

Join 2,000+ developers mastering modern Next.js. Get the next lesson on file-based routing delivered to your inbox - 100% FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

NextJS Tutorials

0 of 70 completed

Your Progress0%

Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo