Introduced in Next.js 13, the App Router takes a fundamentally different approach to routing and rendering compared to the traditional Pages Router. Now stable and widely adopted, let’s break down the basics for those planning to migrate.
App Router vs Pages Router
| Feature | Pages Router | App Router |
|---|---|---|
| Routing | Under pages/ | Under app/ |
| Components | Client Component by default | Server Component by default |
| Layouts | Manual implementation | Nestable layout.js |
| Loading | Manual implementation | Automatic via loading.js |
| Data Fetching | getServerSideProps, etc. | Direct async inside components |
1. File-Based Routing
Folder structure inside app/ maps directly to URL paths.
app/
├── page.js → /
├── layout.js → Shared layout for all pages
├── about/
│ └── page.js → /about
└── blog/
├── page.js → /blog
└── [slug]/
└── page.js → /blog/hello-world (dynamic route)
// app/blog/[slug]/page.js
export default async function BlogPost({ params }) {
const { slug } = await params;
const post = await getPost(slug);
return <article>{post.title}</article>;
}
2. Layout System
layout.js automatically applies to all pages under its folder. Layouts can be nested.
// app/layout.js (root layout)
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<header>Site Header</header>
{children}
<footer>Site Footer</footer>
</body>
</html>
);
}
// app/blog/layout.js (blog section layout)
export default function BlogLayout({ children }) {
return (
<div className="blog-wrapper">
<aside>Sidebar</aside>
<main>{children}</main>
</div>
);
}
3. Loading and Error States
Drop loading.js and error.js into a folder to automatically get loading and error UIs.
// app/blog/loading.js
export default function Loading() {
return <div>Loading...</div>;
}
// app/blog/error.js
'use client';
export default function Error({ error, reset }) {
return (
<div>
<h2>Something went wrong</h2>
<button onClick={reset}>Try again</button>
</div>
);
}
4. Server vs Client Components
In App Router, Server Components are the default — this is the key change.
| Server Component | Client Component | |
|---|---|---|
| Renders on | Server | Browser |
| useState / useEffect | ❌ Not available | ✅ Available |
| Direct DB access | ✅ Possible | ❌ Not possible |
| Bundle size | Small (no JS sent) | Large |
| Interactions | ❌ Not possible | ✅ Possible |
// Server Component (default) — can access DB directly
export default async function PostList() {
const posts = await db.query('SELECT * FROM posts');
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}
Rule of thumb: write Server Components by default, and only use Client Components for the smallest interactive pieces.
5. Data Fetching Patterns
App Router lets you call fetch() directly inside components. Caching is on by default.
// Cached (default)
const data = await fetch('https://api.example.com/posts');
// No cache (fresh every request)
const data = await fetch('https://api.example.com/posts', {
cache: 'no-store',
});
// Time-based revalidation
const data = await fetch('https://api.example.com/posts', {
next: { revalidate: 60 },
});
6. Route Groups
Wrap a folder name in ( ) to organize files without affecting the URL.
app/
├── (marketing)/
│ ├── page.js → /
│ └── pricing/
│ └── page.js → /pricing
└── (shop)/
├── layout.js → Shop-specific layout
└── products/
└── page.js → /products
Great for using different layouts across sections without URL prefixes.
摘要
App Router is a new architecture centered around Server Components, and its philosophy differs significantly from Pages Router. It may be confusing at first, but sticking to “Server Components first, Client Components only where needed” will lead to high-performance applications. For migrations, start with smaller pages and move gradually.

