Next.js App Router vs Pages Router: Which to Use in 2026
The App Router is the default for new projects, but the Pages Router is not deprecated. Here is what actually changed and when migrating is not worth it.
For a new Next.js project in 2026, use the App Router. It is where the framework's development goes, and Server Components remove a whole category of data-fetching plumbing. But the Pages Router is not deprecated — it is still documented, still supported, and there is no announced removal. An existing Pages Router app is not a liability that needs an emergency migration.
That distinction matters, because "App Router vs Pages Router" is really two different questions. For a new project it is a near-default. For an existing one it is a cost-benefit calculation, and the answer is frequently "not yet."
Side by side
App Router (app/) | Pages Router (pages/) | |
|---|---|---|
| Status | Active development | Supported, not deprecated |
| Default component type | Server Component | Client Component |
| Data fetching | async components, fetch in the component | getServerSideProps / getStaticProps |
| Layouts | Nested layout.tsx, persist across navigation | _app.tsx + manual per-page patterns |
| Loading states | loading.tsx + Suspense streaming | Manual, per page |
| Error handling | error.tsx per segment | _error.tsx, app-wide |
| API endpoints | route.ts handlers | pages/api/* |
| Metadata | metadata export / generateMetadata | next/head |
| Mutations | Server Actions | API route + client fetch |
| Ecosystem friction | Some client-only libraries need wrapping | Everything works |
| Learning curve | Steeper — the server/client split is new | Familiar React |
The actual change: where your code runs
Everything in that table follows from one shift. In the Pages Router, every component is a client component; the server's job is to run three special functions and hand the result to React. In the App Router, every component is a Server Component unless you opt out with "use client", and the server renders most of your tree.
This is the whole learning curve. It is not new syntax, it is a new question you now have to answer for every component: does this need to run in the browser?
The practical rule is narrower than people expect. A component needs "use client" only if it uses state, effects, event handlers, or browser APIs. Everything else — layout, text, data display, most of a dashboard's markup — can stay on the server.
The same page, both ways
Pages Router: fetch on the server, pass through props, render on the client.
// pages/orders.tsx
export async function getServerSideProps() {
const res = await fetch("https://api.example.com/orders");
return { props: { orders: await res.json() } };
}
export default function Orders({ orders }) {
return (
<ul>
{orders.map((o) => (
<li key={o.id}>{o.total}</li>
))}
</ul>
);
}
App Router: the component is the data fetch.
// app/orders/page.tsx
export default async function Orders() {
const res = await fetch("https://api.example.com/orders");
const orders = await res.json();
return (
<ul>
{orders.map((o) => (
<li key={o.id}>{o.total}</li>
))}
</ul>
);
}
The second version never serializes orders to the client. On a large list that is a real payload difference, and the API key you used to fetch it never leaves the server — you can query a database directly here without a middle API layer.
The cost is that Orders can no longer use useState. When you need interactivity, you push it down into a small client component rather than making the page one:
// app/orders/page.tsx — stays a Server Component
import OrderFilter from "./OrderFilter";
export default async function Orders() {
const orders = await db.order.findMany();
return <OrderFilter orders={orders} />;
}
// app/orders/OrderFilter.tsx
"use client";
import { useState } from "react";
export default function OrderFilter({ orders }) {
const [query, setQuery] = useState("");
const visible = orders.filter((o) => o.customer.includes(query));
// ...
}
"Push client components to the leaves" is the one architectural habit that makes the App Router click. Get it wrong — a "use client" at the top of your layout — and you have a Pages Router app with extra steps.
What you gain
Layouts that persist. A nested layout.tsx does not remount on navigation. A dashboard sidebar keeps its scroll position and expanded state when you move between pages, for free. Reproducing that in the Pages Router means hoisting state into _app.tsx and being careful.
Streaming. A loading.tsx beside a page makes its fallback stream while data resolves, so the shell paints immediately instead of the whole route waiting on the slowest query. In the Pages Router, getServerSideProps is all-or-nothing: nothing renders until it returns.
Server Actions. Mutations become functions instead of endpoints, and forms work before hydration:
export default function NewPost() {
async function create(formData: FormData) {
"use server";
await db.post.create({ data: { title: formData.get("title") as string } });
}
return (
<form action={create}>
<input name="title" />
<button type="submit">Save</button>
</form>
);
}
Metadata without a component. export const metadata and generateMetadata replace next/head, which means SEO tags are computed on the server and are deduplicated per route segment rather than racing each other.
What it costs
Be honest about these, because they are why teams stall mid-migration.
The server/client boundary is a real learning curve. Every developer on the team hits the same errors — useState in a Server Component, passing a function as a prop across the boundary, importing a client-only library into server code. It is learnable in a week, but it is a week, per person.
params and searchParams are async. Since Next.js 15 they are Promises, so you await them:
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
// ...
}
Most tutorials written before this change are wrong, which makes the error message confusing when you copy one.
Caching has moved, more than once. Fetch caching defaults changed between Next.js 14 and 15, and Next.js 16 continues to develop the model. This is the area where the App Router has been genuinely unstable across versions, and the area where you should read the docs for your installed version rather than trusting a blog post — this one included. Check node_modules/next/dist/docs/ or the docs for your exact minor.
Some libraries still need wrapping. Anything reaching for window at module scope needs a "use client" wrapper or a dynamic import. The ecosystem has mostly caught up, but "mostly" is doing work in that sentence.
When the Pages Router is still the right call
Not a hedge — these are real cases.
- A large, working app with no performance problem. Migration cost is measured in weeks and the payoff is architectural, not user-visible. "It works and we have features to ship" is a legitimate engineering answer.
- A team that ships more value than it would gain. If your bottleneck is product decisions rather than payload size, the migration buys you nothing this quarter.
- Heavy dependence on a client-only library with no server-compatible story. Rare in 2026, not extinct.
- Mostly-static marketing sites.
getStaticPropsis simple, well understood, and produces the same HTML. There is no user-facing win here.
The one case that genuinely forces a move is wanting a feature the Pages Router does not have — streaming, Server Actions, or partial rendering. Otherwise the deadline is not real.
If you do migrate, migrate incrementally
Both routers run in the same project. app/ takes precedence for conflicting routes, so you move one route at a time and ship continuously.
A sequence that works:
- Add
app/alongsidepages/. Nothing breaks; the two coexist. - Move one low-traffic leaf route first — a settings page, an about page. Learn the boundary on something that will not page you at 3am.
- Port shared chrome into
app/layout.tsx. This is where the persistent-layout win shows up. - Move data-heavy routes next. They have the biggest payoff, and by now you know the patterns.
- Convert
pages/apitoroute.tshandlers — mostly mechanical. - Delete
pages/when it is empty. Not before.
Next.js ships codemods that handle the mechanical parts. They do not decide your client boundaries, which is the part that takes judgement.
Mistakes that cost the most time
| Mistake | What you see | Fix |
|---|---|---|
"use client" at the top of the layout | Everything is a client component; no benefit | Push it to the leaf that actually needs state |
Using useState in a Server Component | Build error about hooks | Extract the interactive part into a client component |
Forgetting to await params | Type error, or undefined slug | const { slug } = await params |
| Passing a function as a prop to a client component | "Functions cannot be passed directly" | Pass data; define handlers inside the client component |
| Importing a client-only lib into server code | window is not defined at build | Dynamic import, or wrap in a "use client" module |
Expecting getServerSideProps to work in app/ | Silently ignored | Fetch directly in the async component |
| Migrating everything in one branch | Three-week branch, merge conflicts | One route at a time, ship each |
Frequently asked questions
Is the Pages Router deprecated? No. It is still documented and supported, and there is no announced removal timeline. It receives fixes rather than new features — "legacy" in the sense that development attention is elsewhere, not in the sense that it is going away.
Can I use both in one project?
Yes, and this is the supported migration path. app/ and pages/ coexist, with app/ winning conflicts. Most real migrations run in this state for months.
Is the App Router faster? Sometimes, and not automatically. Server Components can cut client JavaScript significantly, and streaming improves perceived load. But a badly structured App Router app — client boundary at the root, waterfalling requests — can be slower than the Pages Router equivalent. The architecture enables the win; it does not hand it to you.
Which should I learn first if I am new to Next.js? The App Router. It is what new projects use and what the documentation leads with. Learn the Pages Router when you join a team that has one — it is the simpler of the two to pick up afterwards.
Does the App Router work with a static export?
Yes, with output: "export", subject to the usual constraints — no Server Actions, no dynamic route handlers, no runtime-only features. Static-first sites work fine.
Where our templates sit
Every Next.js edition we ship is App Router, because that is what a new project should start on. ASoc Admin shows the pattern at scale — server-rendered dashboard shells with client boundaries pushed down to the charts and forms that need them — and ASoc Vertex is a Next.js-only build if you do not need the other framework editions.
If you are still deciding the broader stack, Next.js vs React + Vite for admin dashboards covers the layer above this decision. For starting points, see our Next.js admin templates and Next.js landing page templates.
