Chapter 01

Next.js 与 App Router 架构

理解 Next.js 15 的核心设计哲学,掌握 App Router 与 Pages Router 的本质区别

1.1 什么是 Next.js

Next.js 是由 Vercel 开发并维护的 React 全栈框架。它在 React 的基础上提供了路由、服务端渲染、数据获取、图片优化等生产级特性,使开发者无需从零配置就能构建高性能的 Web 应用。自 2016 年发布以来,Next.js 已成为全球最受欢迎的 React 框架之一。

2023 年随着 Next.js 13 引入 App Router,2024 年 Next.js 15 进一步稳定了这一架构。App Router 基于 React Server Components(RSC)规范,从根本上改变了 React 应用的渲染模型,将"服务器优先"的思路贯穿整个框架设计。

ℹ️

Next.js 15 新特性React 19 稳定支持、Turbopack 构建工具正式 GA、改进的缓存语义(fetch 默认不再缓存)、`after()` API 支持响应后执行副作用。本教程全程使用 Next.js 15 + React 19。

Next.js 解决了哪些问题

纯 React 应用(Create React App)是客户端渲染(CSR):浏览器下载空 HTML → 下载 JS Bundle → 执行 JS → 渲染页面。这带来三个问题:

Next.js 通过服务端渲染(SSR)、静态生成(SSG)和 React Server Components 三种策略,从架构层面解决了这些问题。

1.2 创建第一个 Next.js 项目

Next.js 官方提供了 create-next-app 脚手架,可以快速创建包含 TypeScript、ESLint、Tailwind CSS 的完整项目模板。

# 使用 pnpm 创建项目(推荐)
pnpm create next-app@latest my-app

# 交互式提示选项:
# ✔ Would you like to use TypeScript? › Yes
# ✔ Would you like to use ESLint? › Yes
# ✔ Would you like to use Tailwind CSS? › Yes
# ✔ Would you like your code inside a `src/` directory? › No
# ✔ Would you like to use App Router? › Yes
# ✔ Would you like to use Turbopack for `next dev`? › Yes
# ✔ Would you like to customize the import alias? › No
SHELL
# 启动开发服务器
cd my-app
pnpm dev
# 访问 http://localhost:3000
SHELL

生成的项目结构

my-app/
├── app/                  # App Router 根目录
│   ├── layout.tsx        # 根布局(必须存在)
│   ├── page.tsx          # 根页面 /
│   ├── globals.css       # 全局样式
│   └── favicon.ico
├── public/               # 静态资源
├── next.config.ts        # Next.js 配置
├── tsconfig.json
└── package.json
STRUCTURE

1.3 App Router 核心约定文件

App Router 使用一套特殊的文件命名约定来定义路由行为。每个文件名都有其专属语义,Next.js 会根据文件名自动决定如何处理请求。这是"约定优于配置"原则的典型体现。

1.4 App Router vs Pages Router 的本质区别

Next.js 同时支持 App Router(app/ 目录)和 Pages Router(pages/ 目录),两者可以在同一项目中共存,但新项目应优先使用 App Router。理解两者的本质区别有助于迁移老项目和做技术选型。

Pages Router(旧)

  • 所有组件默认是客户端组件
  • 通过 getStaticProps / getServerSideProps 获取数据
  • _app.tsx 全局布局,每次导航重渲染
  • 不支持嵌套布局
  • 路由文件即页面文件
  • API 放在 pages/api/ 目录
  • React 18 功能受限

App Router(新)

  • 所有组件默认是服务端组件
  • 直接在组件中使用 async/await 获取数据
  • layout.tsx 嵌套布局,导航时不重渲染
  • 支持深层嵌套布局
  • page.tsx / layout.tsx 分离关注点
  • API 使用 route.ts 定义
  • 全面支持 React 19 新特性
⚠️

迁移注意Pages Router 中的 getServerSidePropsgetStaticProps 在 App Router 中已不再使用。数据获取改为直接在 Server Component 中调用 async/await,或使用新的缓存 API。

渲染模型对比

特性 Pages Router App Router
默认渲染位置客户端服务端
数据获取方式特殊 API 函数async/await 直接调用
布局复用需要手动封装layout.tsx 自动嵌套
流式渲染不支持Suspense + streaming
Server Actions不支持原生支持
React 版本React 18React 19

1.5 根布局与页面组件

App Router 要求 app/layout.tsx 必须存在,这是整个应用的根布局。它包裹所有页面,提供全局 HTML 结构、字体加载、全局 Provider 等。由于根布局输出 <html><body> 标签,请勿在页面组件中重复添加这些标签。

// app/layout.tsx — 根布局(Server Component)
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'My App',
  description: 'Generated by Next.js',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="zh-CN">
      <body className={inter.className}>
        {children}
      </body>
    </html>
  )
}
TSX
// app/page.tsx — 根页面(/ 路由)
// 默认为 Server Component,可以直接使用 async/await
export default async function HomePage() {
  // 服务端直接获取数据,无需 useEffect
  const posts = await fetchPosts()

  return (
    <main>
      <h1>欢迎使用 Next.js 15</h1>
      {posts.map(post => (
        <article key={post.id}>{post.title}</article>
      ))}
    </main>
  )
}
TSX
💡

Server Component 默认行为在 App Router 中,所有 .tsx / .ts 文件默认都是 Server Component。只有在文件顶部添加 'use client' 指令,才会变成 Client Component。这是与 Pages Router 最重要的心智模型转变。

1.6 next.config.ts 配置文件

Next.js 15 将配置文件从 JavaScript 升级为 TypeScript(next.config.ts),提供完整的类型提示。配置文件用于自定义 Next.js 的构建、运行时行为,包括环境变量、图片域名白名单、重写规则、实验性功能开关等。

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  // 图片域名白名单(next/image 远程图片需要)
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'images.unsplash.com',
      },
    ],
  },

  // 实验性功能
  experimental: {
    // PPR: Partial Prerendering(静态 shell + 动态数据流)
    ppr: true,
  },

  // 重写规则(将旧路由映射到新路由)
  async rewrites() {
    return [
      { source: '/old-blog/:slug', destination: '/blog/:slug' },
    ]
  },
}

export default nextConfig
TS