The App Router introduced in Next.js replaces large parts of the older Pages Router with a file-system-based, nested layout system that defaults to React Server Components. It changes how you structure apps, fetch data, and handle routing, enabling faster rendering, streaming, and simpler colocation of UI and data.
What changed (high-level)
- File layout: app/ is the new entry instead of pages/. Routes, layouts, loading and error UI are colocated with components.
- Server-first: components inside app/ are Server Components by default (no client JS unless explicitly opted-in).
- Nested persistent layouts: layout.tsx files define persistent UI across nested routes, reducing re-renders and preserving state.
- Data fetching: use of native fetch with caching and revalidation replaces getServerSideProps/getStaticProps for most cases.
- Route handlers: file-based API routes live in app/ as route.ts and can run on Node or Edge runtimes.
- Streaming & Suspense: server rendering can stream content to the client; loading UI uses loading.tsx and React Suspense.
Key concepts
Server Components (default)
- Components in app/ are rendered on the server by default. They can use async/await, access secrets, and perform data fetching directly.
- Benefits: smaller client bundles, faster first paint, ability to use server-only APIs.
Example (server component):
export default async function Page() {
const res = await fetch('https://api.example.com/posts/1', { cache: 'no-store' })
const post = await res.json()
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
)
}
Note: angle brackets and square brackets in code examples above are escaped as HTML entities.
Client Components
- Add the directive “use client” to the top of a file to opt into client-side behavior (hooks, event handlers, browser APIs).
- Keep these small; they increase client JS.
Example (client component):
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(c => c + 1)}>Clicked {count} times</button>
)
}
Layouts & Nested Routing
- layout.tsx files define persistent UI for a route segment and all nested routes. Each route segment can have its own layout.
- Persistent UI (navigation, sidebars) keeps state across page navigations.
- Files: page.tsx (route UI), layout.tsx (layout for segment), loading.tsx, error.tsx.
File structure example:
app/
layout.tsx
page.tsx
dashboard/
layout.tsx
page.tsx
settings/
page.tsx
Route Handlers (API in app/)
- Create route.ts inside a folder to handle HTTP methods. These are server-only and can return Response objects.
- Can specify runtime (e.g., export const runtime = ‘edge’).
Example (route handler):
export async function GET() {
const data = { message: 'Hello from app route' }
return new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' }
})
}
Data fetching & caching
- Use fetch in Server Components. Next.js augments fetch with caching and revalidation options.
- Common options: { cache: ‘no-store’ } (always fresh), { next: { revalidate: 60 } } (ISR-like), or rely on default caching.
Example with revalidation:
export default async function Page() {
const res = await fetch('https://api.example.com/posts', { next: { revalidate: 60 } })
const posts = await res.json()
return (
<ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
)
}
Streaming & Suspense (loading.tsx)
- Create loading.tsx in a segment to show a placeholder while nested Server Components stream content.
- React Suspense on the server enables progressive rendering.
Example loading UI:
export default function Loading() {
return <div>Loading...</div>
}
Metadata API
- Use export const metadata or generateMetadata function inside route files to set page titles, meta tags, and Open Graph data.
Example:
export const metadata = {
title: 'My Page',
description: 'Simple description'
}
Navigation and client hooks
- The App Router uses hooks from ‘next/navigation’: useRouter, usePathname, useSearchParams.
- useRouter from ‘next/navigation’ is different from the old ‘next/router’.
Example (client use):
'use client'
import { useRouter } from 'next/navigation'
export default function BackButton() {
const router = useRouter()
return <button onClick={() => router.back()}>Back</button>
}
Simple end-to-end example (file layout & files)
File tree (example app):
app/
layout.tsx
page.tsx
blog/
layout.tsx
page.tsx
[slug]/
page.tsx
loading.tsx
api/
hello/route.ts
app/layout.tsx:
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<nav>My site</nav>
<main>{children}</main>
</body>
</html>
)
}
app/blog/[slug]/page.tsx (dynamic route + server fetch):
type Props = { params: { slug: string } }
export default async function PostPage({ params }: Props) {
const res = await fetch(`https://api.example.com/posts/${params.slug}`, { cache: 'no-store' })
if (!res.ok) throw new Error('Failed to fetch')
const post = await res.json()
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
)
}
app/api/hello/route.ts:
export async function GET() {
return new Response(JSON.stringify({ hello: 'world' }), {
headers: { 'Content-Type': 'application/json' }
})
}
Migration tips (quick)
- Start by creating app/ alongside pages/ and migrate one route at a time.
- Convert layouts and split persistent parts into layout.tsx to reduce client re-renders.
- Replace data fetching functions with fetch calls inside Server Components; use revalidate options for ISR behavior.
- Keep client components minimal and mark them with “use client”.
- Use route handlers (route.ts) for API-like endpoints that need server-only logic.
When to use the App Router
- Use the App Router for new projects to leverage server components, streaming, and nested layouts.
- For existing large projects, migrate incrementally. Pages Router remains supported for many use cases.
Conclusion
The App Router is a more modern, server-first approach to building Next.js apps. It provides better defaults (server components), persistent nested layouts, simpler colocation of UI and data, and first-class streaming. Start small: create an app/ layout, move a page, and experience smaller client bundles and faster renders.
