mcpField
N
nextjs-app-router

nextjs-app-router

github.com/giuseppe-trisciuoglio开发3653.9k 次安装7 周前更新
代码仓库

关于 nextjs-app-router

用于使用 Next.js 16+ App Router 开发 React 应用的技能。内容涵盖服务端与客户端组件、Actions、路由处理器、缓存、路由模式及异步 API,适合创建或维护 Next.js 应用的开发者。

主要功能

  1. 01服务端与客户端组件模式
  2. 02Server Actions 和表单变更
  3. 03用于 API 接口的路由处理器
  4. 04显式缓存与重新验证
  5. 05并行路由和拦截路由
  6. 06Next.js 16 异步 API 指引

适用场景

  1. 01搭建 App Router 项目
  2. 02使用 Server Actions 构建表单
  3. 03通过路由处理器创建 API 接口
  4. 04添加缓存页面和并行仪表板路由

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill nextjs-app-router

安装后在 Claude Code 中输入/nextjs-app-router确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
app-router-fundamentals.md
best-practices.md
caching-strategies.md
data-fetching.md
examples.md
metadata-api.md
nextjs16-migration.md
patterns.md
routing-patterns.md
server-actions.md

详细介绍

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

使用 Next.js 16+ 和 App Router 架构构建现代 React 应用。

概览

此技能提供以下模式:服务器组件(默认)和客户端组件("use client")、用于变更和表单处理的服务器操作、用于 API 端点的路由处理器、使用 "use cache" 指令进行显式缓存、并行路由和拦截路由,以及 Next.js 16 的异步 API 和 proxy.ts。

适用场景

当用户请求涉及以下内容时启用:

  • “创建 Next.js 16 项目”、“设置 App Router”
  • “服务器组件”、“客户端组件”、“use client”
  • “服务器操作”、“表单提交”、“变更”
  • “路由处理器”、“API 端点”、“route.ts”
  • “use cache”、“cacheLife”、“cacheTag”、“重新验证”
  • “并行路由”、“@ 插槽”、“拦截路由”
  • “proxy.ts”、“从 middleware.ts 迁移”
  • “layout.tsx”、“page.tsx”、“loading.tsx”、“error.tsx”、“not-found.tsx”
  • “generateMetadata”、“next/image”、“next/font”

快速参考

文件用途指令用途
page.tsx路由页面"use server"服务器操作函数
layout.tsx共享布局"use client"客户端组件边界
loading.tsxSuspense 加载状态"use cache"显式缓存(Next.js 16)
error.tsx错误边界
not-found.tsx404 页面
route.tsAPI 路由处理器
proxy.ts路由边界

说明

创建新项目
npx create-next-app@latest my-app --typescript --tailwind --app --turbopack
服务器组件

服务器组件是 App Router 中的默认组件。它们在服务器上运行,并可使用 async/await。

// app/users/page.tsx
async function getUsers() {
  const apiUrl = process.env.API_URL;
  const res = await fetch(`${apiUrl}/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>;
}
客户端组件

使用 hooks、浏览器 API 或事件处理程序时,添加 "use client"。

"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>;
}
服务器操作

在单独的文件中使用 "use server" 指令定义操作。

// 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 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>
  );
}

参见 references/server-actions.md,了解 Zod 验证、乐观更新和高级模式。

配置缓存

使用 "use cache" 指令进行显式缓存(Next.js 16+)。

"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,了解缓存配置、按需重新验证和高级模式。

路由处理器
// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";

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

export async function POST(request: NextRequest) {
  const body = await request.json();
  return NextResponse.json(await db.user.create({ data: body }), { status: 201 });
}

动态段使用 [param]:

// app/api/users/[id]/route.ts
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
  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);
}
Next.js 16 异步 API

在版本 16 中,所有 Next.js API 都是异步的。

import { cookies, headers } from "next/headers";

export default async function Page() {
  const cookieStore = await cookies();
  const headersList = await headers();
  const session = cookieStore.get("session")?.value;
  const userAgent = headersList.get("user-agent");
  return <div>...</div>;
}

Params 和 searchParams 也基于 Promise:

export default async function Page({
  params,
  searchParams,
}: {
  params: Promise<{ slug: string }>;
  searchParams: Promise<{ sort?: string }>;
}) {
  const { slug } = await params;
  const { sort } = await searchParams;
  // ...
}

参见 references/nextjs16-migration.md,了解迁移指南和 proxy.ts 配置。

并行路由

使用 @folder 约定创建并行路由插槽。

// app/dashboard/layout.tsx
export default function DashboardLayout({ children, team, analytics }: Record<string, React.ReactNode>) {
  return (
    <div>
      {children}
      <div className="grid grid-cols-2">{team}{analytics}</div>
    </div>
  );
}
// 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,了解拦截路由、路由组和动态路由。

最佳实践

服务器端与客户端决策:

  • 从服务器组件(默认)开始
  • 仅在以下情况使用客户端组件:React hooks(useState、useEffect)、浏览器 API(window、document)、事件处理程序(onClick、onSubmit)或仅限客户端的库

数据获取:

  • 尽可能在服务器组件中获取数据
  • 使用 React 的 cache() 去重
  • 并行执行相互独立的数据获取
  • 使用 loading.tsx 添加 Suspense 边界

性能检查清单:

  • 使用 loading.tsx 设置 Suspense 边界
  • 使用 next/image 优化图片
  • 使用 next/font 优化字体
  • 添加 error.tsx 和 not-found.tsx 处理错误

示例

示例 1:使用服务器操作的博客文章表单

输入: 创建一个带有 Zod 验证的博客文章提交表单

输出:

// 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 };
}
// 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>
  );
}
示例 2:带缓存的产品页面

输入: 创建一个支持按需重新验证的缓存产品页面

输出:

// 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></article>;
}
// 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 });
}
示例 3:带并行路由的仪表板

输入: 创建一个包含侧边栏和统计区域的仪表板

输出:

// app/dashboard/layout.tsx
export default function DashboardLayout({ children, sidebar, stats }: Record<string, 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>
  );
}
// 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></>;
}

约束和警告

约束:

  • 服务端组件不能使用浏览器 API 或 React Hooks
  • 客户端组件不能是异步的(不能直接获取数据)
  • 在 Next.js 16 中,cookies()、headers()、draftMode() 是异步的
  • 在 Next.js 16 中,params 和 searchParams 基于 Promise
  • Server Actions 必须使用 "use server" 指令定义

警告:

  • 在客户端组件中使用 await 会导致构建错误
  • 在服务端组件中访问 window 或 document 会抛出错误
  • 在 Next.js 16 中,忘记对 cookies() 或 headers() 使用 await 会返回 Promise,而非实际值
  • 未进行适当验证的 Server Actions 可能会使数据库暴露给未授权访问
  • 外部数据获取:使用 fetch() 获取第三方 URL 的服务端组件会处理不可信内容——务必验证、清理并进行类型检查;使用环境变量配置 API URL,而不要硬编码

参考资料

评论

支持 Markdown