Skip to main content
Practice Problems

App router vs pages router in Next.js

Next.js has two routing systems: Pages Router (pages/ directory) and App Router (app/ directory). App Router was introduced in Next.js 13 and became the recommended approach starting from version 13.4.

Key Differences

AspectPages RouterApp Router
Directorypages/app/
Default componentsClientServer (RSC)
Data fetchinggetServerSideProps, getStaticPropsasync components with fetch
Layouts_app.tsx, _document.tsxNested layout.tsx
Loading UIManual implementationloading.tsx out of the box
Errors_error.tsx globallyerror.tsx per route
StreamingNoYes, via Suspense
Server ActionsNoYes

Pages Router

In the Pages Router every file in the pages/ directory automatically becomes a route. Data is loaded through special functions:

tsx
// pages/problems/index.tsx export async function getServerSideProps() { const res = await fetch('https://api.itlead.org/problems') const problems = await res.json() return { props: { problems } } } export default function ProblemsPage({ problems }) { return ( <ul> {problems.map(p => ( <li key={p.id}>{p.name}</li> ))} </ul> ) }

Layouts are implemented through _app.tsx which wraps all pages:

tsx
// pages/_app.tsx export default function App({ Component, pageProps }) { return ( <Layout> <Component {...pageProps} /> </Layout> ) }

App Router

In the App Router components are server components by default. Data is loaded directly in the component:

tsx
// app/problems/page.tsx import { db } from '@/lib/db' export default async function ProblemsPage() { const problems = await db.problem.findMany() return ( <ul> {problems.map(p => ( <li key={p.id}>{p.name}</li> ))} </ul> ) }

Layouts are nested and preserve state during navigation:

tsx
// app/problems/layout.tsx export default function ProblemsLayout({ children }: { children: React.ReactNode }) { return ( <div className="problems-container"> <Sidebar /> <main>{children}</main> </div> ) }

What App Router Provides

Server Components by Default

Server component code does not end up in the client bundle. Heavy dependencies stay on the server:

tsx
// app/docs/[slug]/page.tsx import { MDXRemote } from 'next-mdx-remote/rsc' import { getDocBySlug } from '@/lib/docs' export default async function DocPage({ params }: { params: { slug: string } }) { const doc = await getDocBySlug(params.slug) return <MDXRemote source={doc.content} /> }

The next-mdx-remote library is not sent to the client. This is exactly how articles are rendered on IT Lead.

Streaming

App Router supports Streaming through loading.tsx files and React Suspense:

tsx
// app/dashboard/loading.tsx export default function Loading() { return <DashboardSkeleton /> }

The user sees the skeleton immediately while data loads progressively.

Nested Layouts

Pages Router has a single global layout. In App Router layouts are nested and do not re-render when navigating between child pages:

  • app

  • layout.tsx

  • docs

  • layout.tsx

  • page.tsx

  • [slug]

  • page.tsx

When to Choose Which

App Router is suitable for new projects. It is the recommended approach and receives all new features.

Pages Router continues to be supported. If a project is already built on Pages Router and works well, migration is not required.

Interview tip:

Both routers can coexist in the same project. This allows gradual migration, one route at a time.

Useful Resources

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems