Next.js 13 was released, and the biggest change is the App Router (/app directory), which takes a completely different approach from Pages Router.
App Router vs Pages Router
pages/(旧) app/(新)
├── index.tsx ├── page.tsx ← 页面组件
├── about.tsx ├── about/
├── api/ │ └── page.tsx
│ └── users.ts ├── layout.tsx ← 布局(新概念)
└── _layout.tsx ├── loading.tsx ← 加载状态(新概念)
├── error.tsx ← 错误边界(新概念)
└── api/
└── users/
└── route.ts ← API 路由
Server Components (Core Changes)
tsx
// app/users/page.tsx
// 默认是 Server Component(服务端执行,不打包到客户端)
async function UsersPage() {
// 直接 async/await,不需要 getServerSideProps
const users = await db.user.findMany(); // 直接访问数据库!
return (
<div>
{users.map((user) => (
<UserCard key={user.id} user={user} />
))}
</div>
);
}
export default UsersPage;
tsx
// app/users/UserCard.tsx
// "use client" 声明客户端组件(有交互、有 hooks)
"use client";
import { useState } from "react";
export function UserCard({ user }: { user: User }) {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div onClick={() => setIsExpanded(!isExpanded)}>
<h2>{user.name}</h2>
{isExpanded && <p>{user.bio}</p>}
</div>
);
}
Key principle: A Server Component can contain a Client Component, but not the other way around.
layout.tsx: Nested Layouts
tsx
// app/layout.tsx(根布局)
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="zh-CN">
<body>
<Navigation />
<main>{children}</main>
<Footer />
</body>
</html>
);
}
// app/dashboard/layout.tsx(嵌套布局)
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex">
<Sidebar />
<div className="flex-1">{children}</div>
</div>
);
}
// 访问 /dashboard/settings:RootLayout → DashboardLayout → SettingsPage
New Data Fetching Approaches
tsx
// 扩展 fetch:自动缓存
async function getData() {
// 默认缓存(静态生成)
const staticData = await fetch("https://api.example.com/posts");
// 每次请求重新获取(服务端渲染)
const dynamicData = await fetch("https://api.example.com/users", {
cache: "no-store",
});
// 60 秒重新验证(增量静态再生成)
const revalidatedData = await fetch("https://api.example.com/config", {
next: { revalidate: 60 },
});
}
Route Handlers (API Routes)
typescript
// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const page = Number(searchParams.get("page")) || 1;
const users = await db.user.findMany({
skip: (page - 1) * 20,
take: 20,
});
return NextResponse.json({ users, page });
}
export async function POST(request: NextRequest) {
const body = await request.json();
const user = await db.user.create({ data: body });
return NextResponse.json(user, { status: 201 });
}
Metadata API (SEO)
tsx
// app/products/[id]/page.tsx
import type { Metadata } from "next";
export async function generateMetadata({ params }): Promise<Metadata> {
const product = await getProduct(params.id);
return {
title: product.name,
description: product.description,
openGraph: {
images: [{ url: product.image }],
},
};
}
My Assessment
App Router is a paradigm-level revolution: Server Components + streaming make "server-first" possible.
But it wasn't mature when first released in November 2022:
- Documentation was incomplete
- Many third-party libraries didn't support Server Components
- Debugging was complex (the server/client boundary isn't easy to understand)
Recommendation: new projects can start using it; for existing projects, wait 6 months for the ecosystem to stabilize before migrating.
Summary
- App Router defaults to Server Components: use async/await directly, not bundled to the client
'use client'declares components that need interactivity and hookslayout.tsxnested layouts are more flexible than_app.js- fetch extends caching control with three strategies (static / dynamic / incremental regeneration)
