关于 nextjs
面向使用 Next.js 15+ App Router 的开发者,提供开发模式指南,涵盖组件、数据获取、路由和性能优化。
主要功能
- 01App Router 文件约定
- 02服务端与客户端组件指南
- 03异步及并行数据获取
- 04路由处理器与 Server Actions
- 05流式渲染与静态生成
适用场景
- 01组织页面与嵌套布局
- 02在服务端组件中获取数据
- 03构建 API 路由与表单操作
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ blencorp/ claude-code-kit --skill nextjs安装后在 Claude Code 中输入/nextjs确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
skill-rules-fragment.jsonresources/data-fetching.mdperformance.mdrouting.mdserver-actions.md详细介绍
Next.js 开发指南
使用 App Router、Server Components 和现代数据获取方式进行 Next.js 15+ 开发的模式。
核心原则
- 服务端优先架构:默认使用 Server Components,仅在需要时使用 Client Components
- 基于文件的路由:使用 App Router 约定来定义页面、布局和路由处理程序
- 数据获取:在 Server Components 中使用 async/await,在需要数据的地方获取数据
- 类型安全:使用 TypeScript 处理路由参数、搜索参数和数据类型
- 性能:通过流式传输、并行数据获取和静态生成进行优化
App Router 结构
文件约定
app/
├── layout.tsx # Root layout (required)
├── page.tsx # Home page
├── loading.tsx # Loading UI
├── error.tsx # Error boundary
├── not-found.tsx # 404 page
├── posts/
│ ├── layout.tsx # Posts layout
│ ├── page.tsx # /posts
│ ├── [id]/
│ │ └── page.tsx # /posts/123
│ └── new/
│ └── page.tsx # /posts/new
└── api/
└── posts/
└── route.ts # API route handler
页面组件
// app/posts/page.tsx
import { getPosts } from '@/lib/api';
export const metadata = {
title: 'Posts',
description: 'Browse all blog posts'
};
export default async function PostsPage() {
const posts = await getPosts();
return (
<div>
<h1>Posts</h1>
<ul>
{posts.map(post => (
<li key={post.id}>
<a href={`/posts/${post.id}`}>{post.title}</a>
</li>
))}
</ul>
</div>
);
}
动态路由
// app/posts/[id]/page.tsx
import { getPost } from '@/lib/api';
import { notFound } from 'next/navigation';
interface PageProps {
params: Promise<{ id: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
}
export async function generateMetadata({ params }: PageProps) {
const { id } = await params;
const post = await getPost(id);
return {
title: post.title,
description: post.excerpt
};
}
export default async function PostPage({ params }: PageProps) {
const { id } = await params;
const post = await getPost(id);
if (!post) {
notFound();
}
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
Server Components 与 Client Components
Server Components(默认)
适用于:
- 获取数据
- 访问后端资源
- 将敏感信息保留在服务端
- 减少客户端 JavaScript
// app/posts/page.tsx (Server Component by default)
import { db } from '@/lib/db';
export default async function PostsPage() {
// Direct database access
const posts = await db.post.findMany();
return <PostList posts={posts} />;
}
Client Components
适用于:
- 事件监听器(onClick、onChange 等)
- 状态和生命周期(useState、useEffect)
- 仅限浏览器的 API
- 自定义 Hooks
// components/SearchBar.tsx
'use client'; // Required directive
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export function SearchBar() {
const [query, setQuery] = useState('');
const router = useRouter();
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
router.push(`/search?q=${query}`);
};
return (
<form onSubmit={handleSubmit}>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Search posts..."
/>
<button type="submit">Search</button>
</form>
);
}
组合模式
// app/posts/page.tsx (Server Component)
import { getPosts } from '@/lib/api';
import { SearchBar } from '@/components/SearchBar'; // Client Component
export default async function PostsPage() {
const posts = await getPosts();
return (
<div>
<SearchBar /> {/* Client Component for interactivity */}
<PostList posts={posts} /> {/* Can be Server Component */}
</div>
);
}
数据获取
基本模式
// Server Component with async/await
export default async function PostsPage() {
const posts = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 } // Cache for 1 hour
}).then(res => res.json());
return <PostList posts={posts} />;
}
并行数据获取
export default async function DashboardPage() {
// Fetch in parallel
const [user, posts, stats] = await Promise.all([
getUser(),
getPosts(),
getStats()
]);
return (
<div>
<UserProfile user={user} />
<PostList posts={posts} />
<Stats data={stats} />
</div>
);
}
使用 Suspense 进行流式传输
import { Suspense } from 'react';
export default function PostsPage() {
return (
<div>
<h1>Posts</h1>
<Suspense fallback={<PostsSkeleton />}>
<Posts />
</Suspense>
</div>
);
}
async function Posts() {
const posts = await getPosts(); // Slow data fetch
return <PostList posts={posts} />;
}
布局
根布局(必需)
// app/layout.tsx
import './globals.css';
export const metadata = {
title: {
default: 'My Blog',
template: '%s | My Blog'
}
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<header>
<nav>{/* Navigation */}</nav>
</header>
<main>{children}</main>
<footer>{/* Footer */}</footer>
</body>
</html>
);
}
嵌套布局
// app/posts/layout.tsx
export default function PostsLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div>
<aside>
<PostsSidebar />
</aside>
<div>{children}</div>
</div>
);
}
路由处理程序(API 路由)
基本处理程序
// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const posts = await getPosts();
return NextResponse.json({ posts });
}
export async function POST(request: NextRequest) {
const body = await request.json();
const post = await createPost(body);
return NextResponse.json({ post }, { status: 201 });
}
动态路由处理程序
// app/api/posts/[id]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
const post = await getPost(params.id);
if (!post) {
return NextResponse.json(
{ error: 'Post not found' },
{ status: 404 }
);
}
return NextResponse.json({ post });
}
Server Actions
基本 Server Action
// app/actions/posts.ts
'use server';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const content = formData.get('content') as string;
const post = await db.post.create({
data: { title, content }
});
revalidatePath('/posts');
redirect(`/posts/${post.id}`);
}
在表单中使用
import { createPost } from '@/app/actions/posts';
export default function NewPostPage() {
return (
<form action={createPost}>
<input name="title" required />
<textarea name="content" required />
<button type="submit">Create Post</button>
</form>
);
}
导航
Link 组件
import Link from 'next/link';
export function PostCard({ post }: { post: Post }) {
return (
<Link href={`/posts/${post.id}`} prefetch={true}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</Link>
);
}
编程式导航
'use client';
import { useRouter } from 'next/navigation';
export function PostActions({ postId }: { postId: string }) {
const router = useRouter();
const handleDelete = async () => {
await deletePost(postId);
router.push('/posts');
router.refresh(); // Refresh server components
};
return <button onClick={handleDelete}>Delete</button>;
}
路由器 Hooks
'use client';
import { usePathname, useSearchParams } from 'next/navigation';
const pathname = usePathname(); // /posts/123
const searchParams = useSearchParams(); // ?q=hello
const query = searchParams.get('q');
元数据
// Static metadata
export const metadata = {
title: 'All Posts',
description: 'Browse our collection of blog posts'
};
// Dynamic metadata
export async function generateMetadata({ params }: PageProps) {
const { id } = await params;
const post = await getPost(id);
return {
title: post.title,
description: post.excerpt
};
}
错误处理
// app/posts/error.tsx
'use client';
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
);
}
// app/posts/[id]/not-found.tsx
export default function NotFound() {
return <div>Post Not Found</div>;
}
静态生成与 ISR
// Generate static pages at build time
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({ id: post.id }));
}
// Revalidate every hour (ISR)
export const revalidate = 3600;
export default async function PostPage({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params;
const post = await getPost(id);
return <Post data={post} />;
}
其他资源
如需详细信息,请参阅:
评论
支持 Markdown