Skip to main content

How does app differ from pages in Next.js?

In short: app is the modern, powerful, and "correct" path, pages is the old, familiar, and still working one.

Now let's break it down clearly and without filler, so it's clear when and why.


General

In Next.js there are two routers:

pagesapp
Statuslegacymain (recommended)
ApproachReact + SPAReact Server Components
Year introduced~20162023+
Futurestill supportedthis is where development goes

How it works

pages/ ├─ index.tsx → / ├─ blog.tsx → /blog └─ users/[id].tsx → /users/1

Characteristics

  • everything is a Client Component
  • data through getServerSideProps, getStaticProps
  • no nested layouts
  • no streaming
  • no server actions
  • _app.tsx, _document.tsx - workarounds for globality
ts
export async function getServerSideProps() { return { props: { data } } }

App Router (app/) - the modern standard

https://nextjs.org/_next/image?q=75&url=https%3A%2F%2Fh8DxKfmAPhn8O0p3.public.blob.vercel-storage.com%2Fdocs%2Flight%2Fproject-organization-colocation.png&w=3840
https://nextjs.org/_next/image?q=75&url=https%3A%2F%2Fh8DxKfmAPhn8O0p3.public.blob.vercel-storage.com%2Fdocs%2Flight%2Fproject-organization-colocation.png&w=3840
https://cdn.sanity.io/images/rdn92ihu/production/17109a56b52f83af20adda513b7f52e01ce803ce-1281x1039.png?auto=format&fit=max&h=1039&w=1281
https://cdn.sanity.io/images/rdn92ihu/production/17109a56b52f83af20adda513b7f52e01ce803ce-1281x1039.png?auto=format&fit=max&h=1039&w=1281

How it works

app/ ├─ layout.tsx ├─ page.tsx → / ├─ blog/ │ └─ page.tsx → /blog └─ users/[id]/ └─ page.tsx → /users/1

Key differences

  • Server Components by default
  • nested layout.tsx
  • streaming + loading states
  • server actions (no API)
  • parallel routes / slots
  • route groups (auth)
  • harder mentally

The main architectural difference

pages

Page = component

app

Page = tree of segments (layout → loading → error → page)


Working with data

pages

ts
getServerSideProps getStaticProps getInitialProps

app

ts
const data = await fetch(...) const data = await prisma.task.findMany()

Directly in the component, with no wrappers.


Layouts

pages

  • global _app.tsx
  • no nesting

app

txt
app/ ├─ layout.tsx # global └─ dashboard/ └─ layout.tsx # only for /dashboard/*

Performance

pagesapp
JS in the browsera lotless
SSRyesyes (smarter)
Streamingnoyes
Partial rendernoyes

Server Actions (killer feature of app)

ts
"use server"; export async function submit(formData: FormData) { await prisma.task.create(...) }
  • no API
  • no fetch
  • type-safe
  • faster

pages does not have this.

Short Answer

Interview ready
Premium

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