nextjs-app-router by giuseppe-trisciuoglio/developer-kit
npx skills add https://github.com/giuseppe-trisciuoglio/developer-kit --skill nextjs-app-router使用 Next.js 16+ 和 App Router 架构构建现代 React 应用程序。
此技能提供了以下模式:
当用户请求涉及以下内容时激活:
@插槽"、"拦截路由"| 文件 | 用途 |
|---|---|
page.tsx | 路由页面组件 |
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
layout.tsx | 共享布局包装器 |
loading.tsx | Suspense 加载 UI |
error.tsx | 错误边界 |
not-found.tsx | 404 页面 |
template.tsx | 重新挂载的布局 |
route.ts | API 路由处理器 |
default.tsx | 并行路由后备组件 |
proxy.ts | 路由边界(Next.js 16) |
| 指令 | 用途 |
|---|---|
"use server" | 标记服务器操作函数 |
"use client" | 标记客户端组件边界 |
"use cache" | 启用显式缓存(Next.js 16) |
npx create-next-app@latest my-app --typescript --tailwind --app --turbopack
服务器组件是 App Router 中的默认组件。
// 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>
);
}
当使用钩子、浏览器 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>
);
}
有关使用 Zod 进行验证、乐观更新和高级模式,请参阅 references/server-actions.md。
使用 "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。
使用路由处理器实现 API 端点。
// 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]:
// 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);
}
在版本 16 中,所有 Next.js API 都是异步的。
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 也是异步的:
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。
使用 @folder 约定来定义并行路由插槽。
// 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>
);
}
// 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。
cache() 进行去重loading.tsx 添加 Suspense 边界"use cache";
import { cacheLife, cacheTag } from "next/cache";
// 设置缓存持续时间
cacheLife("hours");
// 为重新验证添加标签
cacheTag("resource-name");
loading.tsx 作为 Suspense 边界next/image 优化图片next/font 优化字体next.config.ts 中启用 React Compilererror.tsx 处理错误not-found.tsx 处理 404输入: 创建一个带有验证功能的表单来提交博客文章
输出:
// 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>
);
}
输入: 创建一个带有重新验证功能的缓存产品页面
输出:
// 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>
);
}
// 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 });
}
输入: 创建一个带有侧边栏和主内容区域的仪表板
输出:
// 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>
);
}
// 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>
</>
);
}
cookies()、headers()、draftMode() 是异步的params 和 searchParams 是基于 Promise 的await 将导致构建错误window 或 document 将抛出错误cookies() 或 headers() 使用 await 将导致返回 Promise 而不是实际值fetch() 调用第三方 URL)会处理不受信任的内容;在渲染之前,务必对获取的响应进行验证、清理和类型检查,并使用环境变量而非硬编码来设置 API URL。请查阅以下文件以获取详细模式:
每周安装数
251
代码仓库
GitHub 星标数
174
首次出现
2026年2月20日
安全审计
安装于
codex227
gemini-cli225
github-copilot221
cursor221
opencode220
kimi-cli218
Build modern React applications using Next.js 16+ with App Router architecture.
This skill provides patterns for:
Activate when user requests involve:
@slot", "intercepting routes"| File | Purpose |
|---|---|
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) |
| Directive | Purpose |
|---|---|
"use server" | Mark Server Action functions |
"use client" | Mark Client Component boundary |
"use cache" | Enable explicit caching (Next.js 16) |
npx create-next-app@latest my-app --typescript --tailwind --app --turbopack
Server Components are the default in App Router.
// 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>
);
}
Add "use client" when using hooks, browser APIs, or event handlers.
"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>
);
}
Define actions in separate files with "use server" directive.
// 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:
"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 "use cache" directive for explicit caching (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} />;
}
See references/caching-strategies.md for cache profiles, on-demand revalidation, and advanced caching patterns.
Implement API endpoints using Route Handlers.
// 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]:
// 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);
}
All Next.js APIs are async in version 16.
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:
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.
Use @folder convention for parallel route slots.
// 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>
);
}
// 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.
cache() for deduplicationloading.tsx"use cache";
import { cacheLife, cacheTag } from "next/cache";
// Set cache duration
cacheLife("hours");
// Tag for revalidation
cacheTag("resource-name");
loading.tsx for Suspense boundariesnext/image for optimized imagesnext/font for font optimizationnext.config.tserror.tsx for error handlingnot-found.tsx for 404 handlingInput: Create a form to submit blog posts with validation
Output:
// 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>
);
}
Input: Create a cached product page with revalidation
Output:
// 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>
);
}
// 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 });
}
Input: Create a dashboard with sidebar and main content areas
Output:
// 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>
);
}
// 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>
</>
);
}
cookies(), headers(), draftMode() are async in Next.js 16params and searchParams are Promise-based in Next.js 16await in a Client Component will cause a build errorwindow or document in Server Components will throw an errorcookies() or headers() in Next.js 16 will result in a Promise instead of the actual valuesfetch() calls to third-party URLs) process untrusted content; always validate, sanitize, and type-check fetched responses before rendering, and use environment variables for API URLs rather than hardcoding themConsult these files for detailed patterns:
Weekly Installs
251
Repository
GitHub Stars
174
First Seen
Feb 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykWarn
Installed on
codex227
gemini-cli225
github-copilot221
cursor221
opencode220
kimi-cli218
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
106,200 周安装