App Router:你真正需要的 8 个概念

不用上 20 小时的课。这 8 个 App Router 概念就能解释你的 Next.js 16 代码为什么这样跑——含 async params 和缓存改动,正是老教程会踩坑的地方。

App Router 一上来就是一堆新词:server component、client component、layout、route handler、parallel route、intercepting route、server action。对一个独立内容站或小型 SaaS 来说,8 个概念基本覆盖了你日常会碰到的 95%。把这些学扎实,剩下的等真要用到再说。

本文对应 Next.js 16(截至 2026 年 6 月最新补丁是 16.2.7,16.2 于 2026 年 3 月发布)。这个版本有两点会让很多老教程失效:paramssearchParams 现在是异步的;过去”fetch 默认缓存一切”的行为,正被一套显式、opt-in 的缓存模型取代。下面都会讲到,而且代码是真能在 16 上编译通过的。

一句话总结

  • app/ 下的文件默认是 server component;只有需要 state、effect 或浏览器事件时才加 'use client'
  • 文件夹结构就是 URL。layout.tsxpage.tsxloading.tsxnot-found.tsx 是文件约定,不是配置项。
  • 在 Next.js 16 里,paramssearchParamscookies()headers() 都是异步的,必须 await,否则 build 直接报错。
  • 缓存现在是显式的:默认动态,靠 'use cache'(Cache Components)或 fetch 选项 opt-in。旧的隐式 fetch 缓存没了。
  • Server Action('use server')替你省掉了大部分手写的 /api/* 路由——表单提交和 mutation 都能用。

App Router 在 2026 年 6 月的现状

App Router 在 Next.js 13(2023)落地,13.4 转稳定,如今 create-next-app 新建的每个项目都默认用它。Next.js 16(2025 年 10 月)把 Turbopack 设成默认打包器——零配置就能拿到最高 10 倍的 Fast Refresh 和 2–5 倍的生产构建提速。Pages Router 还能用,也能和 App Router 共存,但所有新能力(Partial Prerendering、Cache Components、server action、路由系统重写)都先落在 App Router 上。

它的心智模型和 Pages Router 差别足够大,“我会 React”并不够用。最容易绊倒人的四点转变:渲染默认在服务端、文件系统就是路由、请求期数据现在是异步的、缓存从自动变成了 opt-in。

你撞上其中某个概念的信号

  • build 报错 You're importing a component that needs 'useState'. It only works in a Client Component——你越过了 server / client 边界。
  • 你看到 Route used params.slug. params should be awaited before using its properties——你在 Next.js 15+,却还把 params 当普通对象用。
  • layout.tsx 的子树内导航,它从不重新渲染——这是设计意图,就是共享 layout 的行为。
  • 一个 loading.tsx 文件莫名其妙控制了 Suspense 兜底——文件约定在这里是真实、承重的概念。

8 个概念

1. 默认 Server Component

app/ 下每个文件默认都是 server component,除非首行写了 'use client'。Server component 能直接 await 数据,且给浏览器发送零 JavaScript:

// app/page.tsx —— server component
import { db } from '@/lib/db';

export default async function HomePage() {
  const posts = await db.post.findMany({ take: 10 });
  return (
    <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
  );
}

2. Client Component

'use client' 让这个文件(以及它 import 的一切)进入客户端 bundle。useStateuseEffect、事件处理函数都需要它:

'use client';
import { useState } from 'react';

export function Counter() {
  const [n, setN] = useState(0);
  return <button onClick={() => setN(n + 1)}>计数:{n}</button>;
}

在 server component 里随便 import client component;反过来不行——要把服务端渲染好的结果通过 children 往下传:

// app/page.tsx(server)—— 把服务端渲染的子节点传进客户端壳
import { ClientShell } from './ClientShell';
import { ServerData } from './ServerData';

export default function Page() {
  return <ClientShell><ServerData /></ClientShell>;
}

3. 文件系统路由

文件夹结构就是 URL,没有路由配置文件:

app/
├── layout.tsx              → 包住所有页
├── page.tsx                → /
├── blog/
│   ├── layout.tsx          → 包住 /blog/*
│   ├── page.tsx            → /blog
│   └── [slug]/
│       ├── page.tsx        → /blog/:slug
│       ├── loading.tsx     → Suspense 兜底
│       └── not-found.tsx   → 这段路由内的 404
└── (marketing)/            → route group,不进 URL
    ├── about/page.tsx      → /about
    └── pricing/page.tsx    → /pricing

4. Layout

Layout 包住它的子树,且在子树内导航时重新渲染。这种”保留”正是要点——它让共享的导航、侧栏、客户端 state 在切页时一直活着:

// app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="zh">
      <body>
        <nav>...</nav>           {/* 切换 route 时保留 */}
        {children}
      </body>
    </html>
  );
}

5. 异步的动态 API(Next.js 15/16 的关键改动)

这是抄老教程时最大的坑。从 Next.js 15 起、并在 16 中强制:paramssearchParamscookies()headers()draftMode() 全是 Promise,必须 await。同步解构 params 的老代码现在会 build 失败。

// app/articles/[slug]/page.tsx —— Next.js 16 的签名
export default async function ArticlePage(
  { params }: { params: Promise<{ slug: string }> }
) {
  const { slug } = await params;          // ✅ 要 await
  const article = await getArticle(slug);
  return <article><h1>{article.title}</h1></article>;
}

在 client component 里没法 await,就用 React 的 use hook 把 Promise 拆开:

'use client';
import { use } from 'react';

export function Filters({ searchParams }: { searchParams: Promise<{ q?: string }> }) {
  const { q } = use(searchParams);
  return <span>搜索:{q}</span>;
}

迁移时,官方 codemod 能自动改掉大部分调用点:

npx @next/codemod@canary next-async-request-api .

6. 静态 / 动态 / ISR

路由默认静态。一旦读取请求期数据——await cookies()await headers()await searchParams——这个路由就变动态。增量静态再生成(ISR)就一句 export const revalidate

// app/articles/[slug]/page.tsx
export const revalidate = 3600;   // ISR:最多每小时再生成一次

export async function generateStaticParams() {
  const articles = await getAllArticles();
  return articles.map(a => ({ slug: a.slug }));   // 预构建这些路径
}

generateStaticParams 告诉 Next 哪些动态路径在构建时渲染;没列进去的,会在首次请求时生成(之后按你的 revalidate 缓存)。

7. 缓存:现在是显式的(Cache Components)

老版本 App Router 里,fetch() 结果被隐式缓存,这让不少人措手不及。Next.js 16 把默认值反过来了:一切默认动态(请求期渲染),除非你主动 opt-in。你有三种 opt-in 工具。

HTTP 请求继续用 fetch 选项:

// 每次请求都拉新(16 的默认行为)
const data = await fetch(url, { cache: 'no-store' });

// 按时间重新校验:最多每 60 秒一次
const data = await fetch(url, { next: { revalidate: 60 } });

// 按 tag:之后用 revalidateTag('posts', 'max') 失效
const data = await fetch(url, { next: { tags: ['posts'] } });

要缓存整个函数或组件(不只是 fetch),Next.js 16 加了 'use cache' 指令,靠 next.config.ts 里的 cacheComponents: true 开启。编译器会根据函数输入自动推导缓存 key:

// lib/posts.ts
export async function getPopularPosts() {
  'use cache';
  return db.post.findMany({ orderBy: { views: 'desc' }, take: 5 });
}

失效方式也变了。从 16 起,revalidateTag 第二个参数要传 cacheLife profile 来启用 stale-while-revalidate;Server Action 里还多了 updateTag 用于 read-your-writes:

import { revalidateTag } from 'next/cache';
revalidateTag('posts', 'max');   // 先发旧数据,后台再刷新

8. Server Action

标了 'use server' 的函数能从 client component 调用,但只在服务端运行。它替你省掉大部分手写的 /api/* 路由:

// app/actions.ts
'use server';
import { updateTag } from 'next/cache';
import { db } from '@/lib/db';

export async function createPost(formData: FormData) {
  const title = formData.get('title')?.toString() ?? '';
  await db.post.create({ data: { title } });
  updateTag('posts');   // read-your-writes:调用方立刻看到新帖
}
// app/new/page.tsx —— 直接接到 form 的 action 上
import { createPost } from '../actions';

export default function NewPost() {
  return (
    <form action={createPost}>
      <input name="title" />
      <button type="submit">保存</button>
    </form>
  );
}

两分钟搞定 Metadata

它不在这 8 个里,但你建内容站第一天就会用到。静态页 export 一个 typed metadata 对象,动态的按路由用 generateMetadata。注意这里 params 同样是异步签名:

// app/articles/[slug]/page.tsx
import type { Metadata } from 'next';

export async function generateMetadata(
  { params }: { params: Promise<{ slug: string }> }
): Promise<Metadata> {
  const { slug } = await params;
  const article = await getArticle(slug);
  return {
    title: article.title,
    description: article.description,
    alternates: { canonical: `/articles/${slug}/` },
    openGraph: { title: article.title, url: `/articles/${slug}/` },
  };
}

容易踩的坑

  • “保险起见”每个文件顶部都加 'use client' 你等于把站点退化成 SPA,丢掉了服务端渲染、更小的 bundle 和直接取数。
  • 在 client component 里 import 仅限服务端的库。 轻则 build 报错,重则把密钥泄漏进浏览器 bundle。给绝不能进客户端的模块加 import 'server-only'
  • params / searchParams 当普通对象。 在 16 上这是硬性 build 失败(should be awaited)。永远 await
  • 指望 layout.tsx 在导航时重新渲染。 它不会,这是设计如此。按页变化的 state 放在 page 里,不要放 layout。
  • 以为 fetch 还会自动缓存。 在 16 上不会了——用 cachenext.revalidate'use cache' 明确表态。
  • 还在用 middleware.ts 16 里它被废弃,换成 proxy.ts(逻辑不变、跑 Node.js runtime、名字更清楚)。把文件名和导出的函数都改掉。
  • 把 Server Action 当免费 RPC 跑重活。 它底层就是 HTTP POST,和任何 serverless 函数一样有冷启动代价。

这篇适合谁

2026 年新开 Next.js 项目,或者从 Pages Router 迁过来、想搞清到底变了什么的人。如果你已有 Pages Router 项目又没迁移动力,App Router 能共存,但完整转换是真活——先跑 async-API codemod,再一条路由一条路由地迁。

FAQ

  • 要不要单独学 React Server Components?: 不用。RSC 是 App Router 的地基,学 App Router 就是在学 RSC,不需要再找一份单独的 RSC 教程。
  • 我的老 params 代码怎么突然坏了?: Next.js 15 把 paramssearchParams 变成了异步 Promise,16 强制执行。把组件改成 asyncawait params,或者跑 npx @next/codemod@canary next-async-request-api .
  • 还能用 getStaticProps / getServerSideProps 吗?: 只在 Pages Router 能。App Router 把两者都替代了:async server component 加 fetch 选项搞定静态和 ISR;cache: 'no-store' 或 await cookies() / headers() 强制按请求动态渲染。
  • 隐式 fetch 缓存真的没了吗?: 在开了 Cache Components 的 Next.js 16 里,是的——请求期渲染成了默认,缓存靠 'use cache'fetch 选项 opt-in。这其实才是大多数人一直以为的行为。
  • Server Action 能上生产吗?: 能,Next.js 14 起就稳定了。表单和 mutation 都没问题。但记住它们是公开 endpoint——像对待任何 API 路由一样校验输入、检查鉴权。
  • 必须开 React Compiler 吗?: 不必。它在 16 里已稳定,但要靠 reactCompiler: true opt-in。它会自动 memo 组件,不过因为走 Babel 会拖慢构建——等应用稳定后再开。

相关阅读

外部参考:官方 Next.js 16 升级指南Cache Components 文档

标签: #独立开发 #Next.js #入门 #工作流