
5/22/2026
13
Next.js has revolutionized React with the App Router. Understand the differences between the Pages Router and App Router, Server Components, and when to migrate.

Next.js is the most popular React framework for production, offering built-in optimizations, Server-Side Rendering (SSR), and seamless routing. With the release of Next.js 13 (and refined up through Next.js 15), Vercel introduced a paradigm shift: the App Router.
This new architecture fundamentally changed how developers build Next.js applications, introducing React Server Components (RSC), advanced nested layouts, and new data fetching strategies. However, the legacy Pages Router remains fully supported.
For front-end developers and students, understanding the differences between these two routing paradigms is crucial. In this guide, we break down the App Router vs. Pages Router to help you decide which to use for your next project.
/pages)In the Pages Router, every .js or .tsx file inside the pages directory automatically becomes a route.
pages/index.tsx $\rightarrow$ /pages/about.tsx $\rightarrow$ /aboutpages/blog/[slug].tsx $\rightarrow$ /blog/my-post/app)In the App Router, folders define routes, and the UI is rendered by a special page.tsx file inside those folders.
app/page.tsx $\rightarrow$ /app/about/page.tsx $\rightarrow$ /aboutapp/blog/[slug]/page.tsx $\rightarrow$ /blog/my-postThe App Router allows you to co-locate components, styles, and tests alongside your routes. In the Pages Router, putting a Button.tsx inside the pages directory would accidentally expose it as a web page (/Button). In the App Router, only page.tsx is routable.
The biggest innovation in the App Router is its architecture built around React Server Components (RSC).
By default, all components inside the app directory are Server Components. They are rendered entirely on the server, and no JavaScript is sent to the client for these components.
useState, useEffect) or browser APIs (like window).To use interactive client-side features, you must explicitly mark a component with the "use client" directive at the top of the file:
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
In the Pages Router, components are pre-rendered on the server (SSR/SSG), but they hydrate into full client-side React components in the browser. They ship their JavaScript to the client by default.
getServerSideProps vs. fetch()The Pages Router relies on specific Next.js APIs exported from a page file:
getServerSideProps: For Server-Side Rendering (fetch on every request).getStaticProps: For Static Site Generation (fetch at build time).// Pages Router
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return { props: { data } };
}
The App Router simplifies this by using standard asynchronous JavaScript functions and the extended Web fetch API. You can fetch data directly inside a Server Component:
// App Router
export default async function Page() {
// Fetches data on the server
const res = await fetch('https://api.example.com/data', { cache: 'no-store' });
const data = await res.json();
return <div>{data.title}</div>;
}
You control caching behavior directly using the fetch options (cache: 'force-cache' for static, cache: 'no-store' for dynamic).
Creating persistent layouts (like a Sidebar or Navbar that doesn't re-render on page changes) in the Pages Router required cumbersome workarounds in the _app.tsx file.
The App Router natively supports nested layouts via layout.tsx files. A layout wraps all pages and sub-folders within its directory and maintains its state across navigations.
// app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<section>
<nav>Dashboard Sidebar</nav>
<main>{children}</main> {/* Renders the page.tsx content */}
</section>
);
}
You manage the <head> of your document using the next/head component inside your page render:
import Head from 'next/head';
export default function Page() {
return (
<>
<Head>
<title>My Page</title>
</Head>
<h1>Content</h1>
</>
);
}
The App Router introduces a powerful static and dynamic Metadata API. You export a metadata object or a generateMetadata function from a page.tsx or layout.tsx file, and Next.js handles injecting it into the HTML head.
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'My Page',
description: 'Page description',
};
export default function Page() {
return <h1>Content</h1>;
}
One of the most revolutionary additions in the App Router era is Server Actions. In classic web development (and in the Pages Router), if a user submitted a form, you had to write a client-side fetch request, send it to a separate API route (e.g., pages/api/submit.ts), and handle the response.
Server Actions allow you to define server-side functions that can be called directly from your Client or Server Components. Next.js handles the underlying HTTP POST requests under the hood.
// app/contact/page.tsx
export default function ContactPage() {
// A function that runs strictly on the server
async function handleSubmit(formData: FormData) {
"use server";
const email = formData.get("email");
const message = formData.get("message");
// Perform database insertion directly!
// await db.contacts.create({ email, message });
console.log(`Saved contact: ${email}`);
}
return (
<form action={handleSubmit} className="flex flex-col gap-4 p-6 max-w-md">
<input type="email" name="email" placeholder="Your Email" required className="border p-2 rounded" />
<textarea name="message" placeholder="Message" required className="border p-2 rounded" />
<button type="submit" className="bg-emerald-500 text-white p-2 rounded hover:bg-emerald-600">
Submit Form
</button>
</form>
);
}
By utilizing Server Actions, you reduce the boilerplate code of writing API endpoints, validate forms directly on the server, and achieve a seamless, type-safe full-stack developer experience.
/app directory.Fortunately, Next.js allows the /app and /pages directories to coexist in the same project. You can migrate your application step-by-step:
/about or /privacy) from pages/about.tsx to app/about/page.tsx.app/layout.tsx.getServerSideProps pages into async Server Components using normal fetch() calls."use client" directive to interactive components (such as sliders, forms, or modal triggers) and keep the parent wrapper as a Server Component.The Next.js App Router represents a massive leap forward in React development, embracing Server Components to ship less JavaScript and build faster applications. While it requires unlearning some old patterns and mastering the mental model of Server vs. Client boundaries, it provides a superior developer experience and better performance out of the box.
Suggested Images:
/pages vs /app folder structures with React logos. (Prompt: Next.js App router vs Pages router concept, neon Vercel colors).Alt Texts:
Internal Linking Suggestions:
Loading comments...