nextjs-app-router

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Next.js App Router (Next.js 16+)

Next.js App Router(Next.js 16+)

Build modern React applications using Next.js 16+ with App Router architecture.
使用Next.js 16+的App Router架构构建现代化React应用。

Overview

概述

This skill provides patterns for:
  • Server Components (default) and Client Components ("use client")
  • Server Actions for mutations and form handling
  • Route Handlers for API endpoints
  • Explicit caching with "use cache" directive
  • Parallel and intercepting routes
  • Next.js 16 async APIs and proxy.ts
本技能提供以下场景的实现模式:
  • Server Components(默认)与Client Components("use client")
  • 用于数据变更和表单处理的Server Actions
  • 用于API端点的Route Handlers
  • 基于"use cache"指令的显式缓存
  • 并行路由与拦截路由
  • Next.js 16异步API与proxy.ts

When to Use

适用场景

Activate when user requests involve:
  • "Create a Next.js 16 project", "Set up App Router"
  • "Server Component", "Client Component", "use client"
  • "Server Action", "form submission", "mutation"
  • "Route Handler", "API endpoint", "route.ts"
  • "use cache", "cacheLife", "cacheTag", "revalidation"
  • "parallel routes", "@slot", "intercepting routes"
  • "proxy.ts", "migrate from middleware.ts"
  • "layout.tsx", "page.tsx", "loading.tsx", "error.tsx", "not-found.tsx"
  • "generateMetadata", "next/image", "next/font"
当用户需求涉及以下内容时启用:
  • "创建Next.js 16项目"、"设置App Router"
  • "Server Component"、"Client Component"、"use client"
  • "Server Action"、"表单提交"、"数据变更"
  • "Route Handler"、"API端点"、"route.ts"
  • "use cache"、"cacheLife"、"cacheTag"、"重新验证"
  • "并行路由"、"@slot"、"拦截路由"
  • "proxy.ts"、"从middleware.ts迁移"
  • "layout.tsx"、"page.tsx"、"loading.tsx"、"error.tsx"、"not-found.tsx"
  • "generateMetadata"、"next/image"、"next/font"

Quick Reference

快速参考

File Conventions

文件约定

FilePurpose
page.tsx
Route page component
layout.tsx
Shared layout wrapper
loading.tsx
Suspense loading UI
error.tsx
Error boundary
not-found.tsx
404 page
template.tsx
Re-mounting layout
route.ts
API Route Handler
default.tsx
Parallel route fallback
proxy.ts
Routing boundary (Next.js 16)
文件用途
page.tsx
路由页面组件
layout.tsx
共享布局包装器
loading.tsx
Suspense加载UI
error.tsx
错误边界
not-found.tsx
404页面
template.tsx
可重新挂载的布局
route.ts
API路由处理器
default.tsx
并行路由回退组件
proxy.ts
路由边界(Next.js 16)

Directives

指令

DirectivePurpose
"use server"
Mark Server Action functions
"use client"
Mark Client Component boundary
"use cache"
Enable explicit caching (Next.js 16)
指令用途
"use server"
标记Server Action函数
"use client"
标记Client Component边界
"use cache"
启用显式缓存(Next.js 16+)

Instructions

操作指南

Create New Project

创建新项目

bash
npx create-next-app@latest my-app --typescript --tailwind --app --turbopack
bash
npx create-next-app@latest my-app --typescript --tailwind --app --turbopack

Implement Server Component

实现Server Component

Server Components are the default in App Router.
tsx
// app/users/page.tsx
async function getUsers() {
  const res = await fetch('https://api.example.com/users');
  return res.json();
}

export default async function UsersPage() {
  const users = await getUsers();
  return (
    <main>
      {users.map(user => <UserCard key={user.id} user={user} />)}
    </main>
  );
}
在App Router中,Server Component是默认类型。
tsx
// app/users/page.tsx
async function getUsers() {
  const res = await fetch('https://api.example.com/users');
  return res.json();
}

export default async function UsersPage() {
  const users = await getUsers();
  return (
    <main>
      {users.map(user => <UserCard key={user.id} user={user} />)}
    </main>
  );
}

Implement Client Component

实现Client Component

Add
"use client"
when using hooks, browser APIs, or event handlers.
tsx
"use client";

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}
当使用React Hooks、浏览器API或事件处理器时,添加
"use client"
指令。
tsx
"use client";

import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(c => c + 1)}>
      Count: {count}
    </button>
  );
}

Create Server Action

创建Server Action

Define actions in separate files with "use server" directive.
tsx
// app/actions.ts
"use server";

import { revalidatePath } from "next/cache";

export async function createUser(formData: FormData) {
  const name = formData.get("name") as string;
  const email = formData.get("email") as string;

  await db.user.create({ data: { name, email } });
  revalidatePath("/users");
}
Use with forms in Client Components:
tsx
"use client";

import { useActionState } from "react";
import { createUser } from "./actions";

export default function UserForm() {
  const [state, formAction, pending] = useActionState(createUser, {});

  return (
    <form action={formAction}>
      <input name="name" />
      <input name="email" type="email" />
      <button type="submit" disabled={pending}>
        {pending ? "Creating..." : "Create"}
      </button>
    </form>
  );
}
See references/server-actions.md for validation with Zod, optimistic updates, and advanced patterns.
在单独文件中使用"use server"指令定义动作。
tsx
// app/actions.ts
"use server";

import { revalidatePath } from "next/cache";

export async function createUser(formData: FormData) {
  const name = formData.get("name") as string;
  const email = formData.get("email") as string;

  await db.user.create({ data: { name, email } });
  revalidatePath("/users");
}
在Client Component中结合表单使用:
tsx
"use client";

import { useActionState } from "react";
import { createUser } from "./actions";

export default function UserForm() {
  const [state, formAction, pending] = useActionState(createUser, {});

  return (
    <form action={formAction}>
      <input name="name" />
      <input name="email" type="email" />
      <button type="submit" disabled={pending}>
        {pending ? "Creating..." : "Create"}
      </button>
    </form>
  );
}
如需了解使用Zod验证、乐观更新及进阶模式,请参考references/server-actions.md

Configure Caching

配置缓存

Use "use cache" directive for explicit caching (Next.js 16+).
tsx
"use cache";

import { cacheLife, cacheTag } from "next/cache";

export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;

  cacheTag(`product-${id}`);
  cacheLife("hours");

  const product = await fetchProduct(id);
  return <ProductDetail product={product} />;
}
See references/caching-strategies.md for cache profiles, on-demand revalidation, and advanced caching patterns.
使用"use cache"指令实现显式缓存(Next.js 16+)。
tsx
"use cache";

import { cacheLife, cacheTag } from "next/cache";

export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;

  cacheTag(`product-${id}`);
  cacheLife("hours");

  const product = await fetchProduct(id);
  return <ProductDetail product={product} />;
}
如需了解缓存配置文件、按需重新验证及进阶缓存模式,请参考references/caching-strategies.md

Create Route Handler

创建Route Handler

Implement API endpoints using Route Handlers.
ts
// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  const users = await db.user.findMany();
  return NextResponse.json(users);
}

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 });
}
Dynamic segments use
[param]
:
ts
// app/api/users/[id]/route.ts
interface RouteParams {
  params: Promise<{ id: string }>;
}

export async function GET(request: NextRequest, { params }: RouteParams) {
  const { id } = await params;
  const user = await db.user.findUnique({ where: { id } });

  if (!user) {
    return NextResponse.json({ error: "Not found" }, { status: 404 });
  }

  return NextResponse.json(user);
}
使用Route Handlers实现API端点。
ts
// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  const users = await db.user.findMany();
  return NextResponse.json(users);
}

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 });
}
动态路由段使用
[param]
命名:
ts
// app/api/users/[id]/route.ts
interface RouteParams {
  params: Promise<{ id: string }>;
}

export async function GET(request: NextRequest, { params }: RouteParams) {
  const { id } = await params;
  const user = await db.user.findUnique({ where: { id } });

  if (!user) {
    return NextResponse.json({ error: "Not found" }, { status: 404 });
  }

  return NextResponse.json(user);
}

Handle Next.js 16 Async APIs

处理Next.js 16异步API

All Next.js APIs are async in version 16.
tsx
import { cookies, headers, draftMode } from "next/headers";

export default async function Page() {
  const cookieStore = await cookies();
  const headersList = await headers();
  const { isEnabled } = await draftMode();

  const session = cookieStore.get("session")?.value;
  const userAgent = headersList.get("user-agent");

  return <div>...</div>;
}
Params and searchParams are also async:
tsx
export default async function Page({
  params,
  searchParams,
}: {
  params: Promise<{ slug: string }>;
  searchParams: Promise<{ sort?: string }>;
}) {
  const { slug } = await params;
  const { sort } = await searchParams;
  // ...
}
See references/nextjs16-migration.md for migration guide and proxy.ts configuration.
在Next.js 16中,所有框架API均为异步类型。
tsx
import { cookies, headers, draftMode } from "next/headers";

export default async function Page() {
  const cookieStore = await cookies();
  const headersList = await headers();
  const { isEnabled } = await draftMode();

  const session = cookieStore.get("session")?.value;
  const userAgent = headersList.get("user-agent");

  return <div>...</div>;
}
Params和searchParams同样为异步类型:
tsx
export default async function Page({
  params,
  searchParams,
}: {
  params: Promise<{ slug: string }>;
  searchParams: Promise<{ sort?: string }>;
}) {
  const { slug } = await params;
  const { sort } = await searchParams;
  // ...
}
如需了解迁移指南及proxy.ts配置,请参考references/nextjs16-migration.md

Implement Parallel Routes

实现并行路由

Use
@folder
convention for parallel route slots.
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode;
  team: React.ReactNode;
  analytics: React.ReactNode;
}) {
  return (
    <div>
      {children}
      <div className="grid grid-cols-2">
        {team}
        {analytics}
      </div>
    </div>
  );
}
tsx
// app/dashboard/@team/page.tsx
export default function TeamPage() {
  return <div>Team Section</div>;
}

// app/dashboard/@analytics/page.tsx
export default function AnalyticsPage() {
  return <div>Analytics Section</div>;
}
See references/routing-patterns.md for intercepting routes, route groups, and dynamic routes.
使用
@folder
约定定义并行路由插槽。
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode;
  team: React.ReactNode;
  analytics: React.ReactNode;
}) {
  return (
    <div>
      {children}
      <div className="grid grid-cols-2">
        {team}
        {analytics}
      </div>
    </div>
  );
}
tsx
// app/dashboard/@team/page.tsx
export default function TeamPage() {
  return <div>Team Section</div>;
}

// app/dashboard/@analytics/page.tsx
export default function AnalyticsPage() {
  return <div>Analytics Section</div>;
}
如需了解拦截路由、路由组及动态路由,请参考references/routing-patterns.md

Best Practices

最佳实践

Server vs Client Decision

Server与Client组件选择

  1. Start with Server Component (default)
  2. Use Client Component only for:
    • React hooks (useState, useEffect, useContext)
    • Browser APIs (window, document, localStorage)
    • Event handlers (onClick, onSubmit)
    • Client-only libraries
  1. 优先使用Server Component(默认类型)
  2. 仅在以下场景使用Client Component:
    • React Hooks(useState、useEffect、useContext)
    • 浏览器API(window、document、localStorage)
    • 事件处理器(onClick、onSubmit)
    • 仅客户端可用的库

Data Fetching

数据获取

  • Fetch in Server Components when possible
  • Use React's
    cache()
    for deduplication
  • Parallelize independent fetches
  • Add Suspense boundaries with
    loading.tsx
  • 尽可能在Server Component中获取数据
  • 使用React的
    cache()
    实现请求去重
  • 并行执行独立的数据请求
  • 通过
    loading.tsx
    添加Suspense边界

Caching Strategy

缓存策略

tsx
"use cache";
import { cacheLife, cacheTag } from "next/cache";

// Set cache duration
cacheLife("hours");

// Tag for revalidation
cacheTag("resource-name");
tsx
"use cache";
import { cacheLife, cacheTag } from "next/cache";

// 设置缓存时长
cacheLife("hours");

// 为重新验证添加标签
cacheTag("resource-name");

Performance Checklist

性能检查清单

  • Use
    loading.tsx
    for Suspense boundaries
  • Use
    next/image
    for optimized images
  • Use
    next/font
    for font optimization
  • Enable React Compiler in
    next.config.ts
  • Add
    error.tsx
    for error handling
  • Add
    not-found.tsx
    for 404 handling
  • 使用
    loading.tsx
    实现Suspense边界
  • 使用
    next/image
    优化图片加载
  • 使用
    next/font
    优化字体加载
  • next.config.ts
    中启用React Compiler
  • 添加
    error.tsx
    处理错误
  • 添加
    not-found.tsx
    处理404场景

Examples

示例

Example 1: Create Blog Post with Server Action

示例1:使用Server Action创建博客文章

Input: Create a form to submit blog posts with validation
Output:
tsx
// app/blog/actions.ts
"use server";

import { z } from "zod";
import { revalidatePath } from "next/cache";

const schema = z.object({
  title: z.string().min(5),
  content: z.string().min(10),
});

export async function createPost(formData: FormData) {
  const parsed = schema.safeParse({
    title: formData.get("title"),
    content: formData.get("content"),
  });

  if (!parsed.success) {
    return { errors: parsed.error.flatten().fieldErrors };
  }

  await db.post.create({ data: parsed.data });
  revalidatePath("/blog");
  return { success: true };
}
tsx
// app/blog/new/page.tsx
"use client";

import { useActionState } from "react";
import { createPost } from "../actions";

export default function NewPostPage() {
  const [state, formAction, pending] = useActionState(createPost, {});

  return (
    <form action={formAction}>
      <input name="title" placeholder="Title" />
      {state.errors?.title && <span>{state.errors.title[0]}</span>}

      <textarea name="content" placeholder="Content" />
      {state.errors?.content && <span>{state.errors.content[0]}</span>}

      <button type="submit" disabled={pending}>
        {pending ? "Publishing..." : "Publish"}
      </button>
    </form>
  );
}
输入: 创建带验证的博客文章提交表单
输出:
tsx
// app/blog/actions.ts
"use server";

import { z } from "zod";
import { revalidatePath } from "next/cache";

const schema = z.object({
  title: z.string().min(5),
  content: z.string().min(10),
});

export async function createPost(formData: FormData) {
  const parsed = schema.safeParse({
    title: formData.get("title"),
    content: formData.get("content"),
  });

  if (!parsed.success) {
    return { errors: parsed.error.flatten().fieldErrors };
  }

  await db.post.create({ data: parsed.data });
  revalidatePath("/blog");
  return { success: true };
}
tsx
// app/blog/new/page.tsx
"use client";

import { useActionState } from "react";
import { createPost } from "../actions";

export default function NewPostPage() {
  const [state, formAction, pending] = useActionState(createPost, {});

  return (
    <form action={formAction}>
      <input name="title" placeholder="Title" />
      {state.errors?.title && <span>{state.errors.title[0]}</span>}

      <textarea name="content" placeholder="Content" />
      {state.errors?.content && <span>{state.errors.content[0]}</span>}

      <button type="submit" disabled={pending}>
        {pending ? "Publishing..." : "Publish"}
      </button>
    </form>
  );
}

Example 2: Product Page with Caching

示例2:带缓存的商品详情页

Input: Create a cached product page with revalidation
Output:
tsx
// app/products/[id]/page.tsx
"use cache";

import { cacheLife, cacheTag } from "next/cache";

export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;

  cacheTag(`product-${id}`, "products");
  cacheLife("hours");

  const product = await db.product.findUnique({ where: { id } });

  if (!product) {
    notFound();
  }

  return (
    <article>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>${product.price}</p>
    </article>
  );
}
tsx
// app/api/revalidate/route.ts
import { revalidateTag } from "next/cache";
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  const { tag } = await request.json();
  revalidateTag(tag);
  return NextResponse.json({ revalidated: true });
}
输入: 创建支持重新验证的缓存商品页面
输出:
tsx
// app/products/[id]/page.tsx
"use cache";

import { cacheLife, cacheTag } from "next/cache";

export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;

  cacheTag(`product-${id}`, "products");
  cacheLife("hours");

  const product = await db.product.findUnique({ where: { id } });

  if (!product) {
    notFound();
  }

  return (
    <article>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>${product.price}</p>
    </article>
  );
}
tsx
// app/api/revalidate/route.ts
import { revalidateTag } from "next/cache";
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  const { tag } = await request.json();
  revalidateTag(tag);
  return NextResponse.json({ revalidated: true });
}

Example 3: Dashboard with Parallel Routes

示例3:带并行路由的仪表盘

Input: Create a dashboard with sidebar and main content areas
Output:
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
  sidebar,
  stats,
}: {
  children: React.ReactNode;
  sidebar: React.ReactNode;
  stats: React.ReactNode;
}) {
  return (
    <div className="flex">
      <aside className="w-64">{sidebar}</aside>
      <main className="flex-1">
        <div className="grid grid-cols-3">{stats}</div>
        {children}
      </main>
    </div>
  );
}
tsx
// app/dashboard/@sidebar/page.tsx
export default function Sidebar() {
  return <nav>{/* Navigation links */}</nav>;
}

// app/dashboard/@stats/page.tsx
export default async function Stats() {
  const stats = await fetchStats();
  return (
    <>
      <div>Users: {stats.users}</div>
      <div>Orders: {stats.orders}</div>
      <div>Revenue: {stats.revenue}</div>
    </>
  );
}
输入: 创建包含侧边栏和主内容区的仪表盘
输出:
tsx
// app/dashboard/layout.tsx
export default function DashboardLayout({
  children,
  sidebar,
  stats,
}: {
  children: React.ReactNode;
  sidebar: React.ReactNode;
  stats: React.ReactNode;
}) {
  return (
    <div className="flex">
      <aside className="w-64">{sidebar}</aside>
      <main className="flex-1">
        <div className="grid grid-cols-3">{stats}</div>
        {children}
      </main>
    </div>
  );
}
tsx
// app/dashboard/@sidebar/page.tsx
export default function Sidebar() {
  return <nav>{/* Navigation links */}</nav>;
}

// app/dashboard/@stats/page.tsx
export default async function Stats() {
  const stats = await fetchStats();
  return (
    <>
      <div>Users: {stats.users}</div>
      <div>Orders: {stats.orders}</div>
      <div>Revenue: {stats.revenue}</div>
    </>
  );
}

Constraints and Warnings

限制与注意事项

Constraints

限制

  • Server Components cannot use browser APIs or React hooks
  • Client Components cannot be async (no direct data fetching)
  • cookies()
    ,
    headers()
    ,
    draftMode()
    are async in Next.js 16
  • params
    and
    searchParams
    are Promise-based in Next.js 16
  • Server Actions must be defined with "use server" directive
  • Server Components无法使用浏览器API或React Hooks
  • Client Components不能是异步类型(无法直接获取数据)
  • 在Next.js 16中,
    cookies()
    headers()
    draftMode()
    均为异步函数
  • 在Next.js 16中,
    params
    searchParams
    基于Promise实现
  • Server Actions必须使用"use server"指令标记

Warnings

注意事项

  • Attempting to use
    await
    in a Client Component will cause a build error
  • Accessing
    window
    or
    document
    in Server Components will throw an error
  • Forgetting to await
    cookies()
    or
    headers()
    in Next.js 16 will result in a Promise instead of the actual values
  • Server Actions without proper validation can expose your database to unauthorized access
  • 在Client Component中使用
    await
    会导致构建错误
  • 在Server Components中访问
    window
    document
    会抛出错误
  • 在Next.js 16中,若未等待
    cookies()
    headers()
    ,将返回Promise而非实际值
  • 未添加验证的Server Actions可能导致数据库被未授权访问

References

参考资料

Consult these files for detailed patterns:
  • references/app-router-fundamentals.md - Server/Client Components, file conventions, navigation, next/image, next/font
  • references/routing-patterns.md - Parallel routes, intercepting routes, route groups, dynamic routes
  • references/caching-strategies.md - "use cache", cacheLife, cacheTag, revalidation
  • references/server-actions.md - Server Actions, useActionState, validation, optimistic updates
  • references/nextjs16-migration.md - Async APIs, proxy.ts, Turbopack, config
  • references/data-fetching.md - Data patterns, Suspense, streaming
  • references/metadata-api.md - generateMetadata, OpenGraph, sitemap
如需了解详细模式,请查阅以下文件:
  • references/app-router-fundamentals.md - Server/Client组件、文件约定、导航、next/image、next/font
  • references/routing-patterns.md - 并行路由、拦截路由、路由组、动态路由
  • references/caching-strategies.md - "use cache"、cacheLife、cacheTag、重新验证
  • references/server-actions.md - Server Actions、useActionState、验证、乐观更新
  • references/nextjs16-migration.md - 异步API、proxy.ts、Turbopack、配置
  • references/data-fetching.md - 数据模式、Suspense、流式传输
  • references/metadata-api.md - generateMetadata、OpenGraph、站点地图