关于 nextjs-development
面向使用 App Router 和全栈模式构建 Next.js 应用的开发者,涵盖路由、渲染、数据获取、API 处理器及生产实践,适用于 Web 应用和内容站点。
主要功能
- 01App Router 与文件路由
- 02Server 和 Client Components
- 03缓存与数据重新验证
- 04API Route Handlers 和中间件
- 05元数据与 SEO 模式
- 06图像优化与 Server Actions
适用场景
- 01构建全栈 React 应用
- 02创建支持增量重新验证的静态站点
- 03实现 API 后端和路由处理器
- 04开发注重 SEO 的内容站点
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ manutej/ luxor-claude-marketplace --skill nextjs-development安装后在 Claude Code 中输入/nextjs-development确认已加载。
附带文件EXAMPLES.mdREADME.md
详细介绍
Next.js 开发技能
本技能根据 Next.js 官方文档,全面介绍如何使用 App Router、Server Components、数据获取模式、路由、API 路由、中间件和全栈开发技术构建现代 Next.js 应用。
何时使用此技能
在以下情况使用此技能:
- 构建带有服务器端渲染(SSR)的全栈 React 应用
- 创建支持增量静态再生成(ISR)的静态网站
- 使用 React Server Components 开发现代 Web 应用
- 使用无服务器路由处理程序构建 API 后端
- 实现包含元数据和 Open Graph 的 SEO 优化应用
- 使用内置优化功能创建可用于生产环境的 Web 应用
- 构建电商网站、博客、仪表板或内容驱动型网站
- 实现身份验证、数据获取和复杂路由模式
- 自动优化图片、字体和性能
- 部署具备边缘计算能力的无服务器应用
核心概念
App Router
App Router 是 Next.js 基于 React Server Components 构建的现代路由系统。它使用 app 目录实现基于文件的路由,并提供增强功能。
基本应用结构:
app/
├── layout.tsx # Root layout (required)
├── page.tsx # Home page
├── loading.tsx # Loading UI
├── error.tsx # Error UI
├── not-found.tsx # 404 page
├── about/
│ └── page.tsx # /about route
└── blog/
├── page.tsx # /blog route
├── [slug]/
│ └── page.tsx # /blog/[slug] dynamic route
└── layout.tsx # Blog layout
根布局(必需):
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
页面组件:
// app/page.tsx
export default function HomePage() {
return (
<main>
<h1>Welcome to Next.js</h1>
<p>Building modern web applications</p>
</main>
)
}
Server Components
Server Components 是在服务器上渲染的 React 组件。它们是 App Router 中的默认组件,可提供更好的性能。
Server Component(默认):
// app/posts/page.tsx
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
cache: 'force-cache' // Static generation
})
return res.json()
}
export default async function PostsPage() {
const posts = await getPosts()
return (
<div>
<h1>Blog Posts</h1>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
)
}
Client Component(需要时使用):
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
)
}
混合使用 Server 和 Client Components:
// app/dashboard/page.tsx (Server Component)
import ClientCounter from './ClientCounter'
async function getData() {
const res = await fetch('https://api.example.com/data')
return res.json()
}
export default async function DashboardPage() {
const data = await getData()
return (
<div>
<h1>Dashboard</h1>
<p>Server data: {data.value}</p>
{/* Client component for interactivity */}
<ClientCounter />
</div>
)
}
数据获取
Next.js 扩展了原生 fetch() API,加入了自动缓存和重新验证功能。
静态数据获取(默认):
async function getStaticData() {
const res = await fetch('https://api.example.com/data', {
cache: 'force-cache' // Default, equivalent to getStaticProps
})
return res.json()
}
export default async function Page() {
const data = await getStaticData()
return <div>{data.title}</div>
}
动态数据获取:
async function getDynamicData() {
const res = await fetch('https://api.example.com/data', {
cache: 'no-store' // Equivalent to getServerSideProps
})
return res.json()
}
export default async function Page() {
const data = await getDynamicData()
return <div>{data.title}</div>
}
重新验证(ISR):
async function getRevalidatedData() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 60 } // Revalidate every 60 seconds
})
return res.json()
}
export default async function Page() {
const data = await getRevalidatedData()
return <div>{data.title}</div>
}
并行数据获取:
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`)
return res.json()
}
async function getUserPosts(id: string) {
const res = await fetch(`https://api.example.com/users/${id}/posts`)
return res.json()
}
export default async function UserPage({ params }: { params: { id: string } }) {
// Fetch in parallel
const [user, posts] = await Promise.all([
getUser(params.id),
getUserPosts(params.id)
])
return (
<div>
<h1>{user.name}</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
)
}
顺序数据获取:
async function getUser(id: string) {
const res = await fetch(`https://api.example.com/users/${id}`)
return res.json()
}
async function getRecommendations(preferences: string[]) {
const res = await fetch('https://api.example.com/recommendations', {
method: 'POST',
body: JSON.stringify({ preferences })
})
return res.json()
}
export default async function UserPage({ params }: { params: { id: string } }) {
// First fetch user
const user = await getUser(params.id)
// Then fetch recommendations based on user data
const recommendations = await getRecommendations(user.preferences)
return (
<div>
<h1>{user.name}</h1>
<h2>Recommendations</h2>
<ul>
{recommendations.map((item) => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</div>
)
}
路由
Next.js 在 app 目录中使用基于文件系统的路由。
动态路由:
// app/blog/[slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string } }) {
return <h1>Post: {params.slug}</h1>
}
// Generates static pages for these slugs at build time
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json())
return posts.map((post) => ({
slug: post.slug,
}))
}
全匹配路由:
// app/docs/[...slug]/page.tsx
export default function DocsPage({ params }: { params: { slug: string[] } }) {
// /docs/a/b/c -> params.slug = ['a', 'b', 'c']
return <h1>Docs: {params.slug.join('/')}</h1>
}
可选全匹配路由:
// app/shop/[[...slug]]/page.tsx
export default function ShopPage({ params }: { params: { slug?: string[] } }) {
// /shop -> params.slug = undefined
// /shop/clothes -> params.slug = ['clothes']
// /shop/clothes/tops -> params.slug = ['clothes', 'tops']
return <h1>Shop: {params.slug?.join('/') || 'All'}</h1>
}
路由组:
app/
├── (marketing)/ # Route group (not in URL)
│ ├── about/
│ │ └── page.tsx # /about
│ └── contact/
│ └── page.tsx # /contact
└── (shop)/
├── products/
│ └── page.tsx # /products
└── cart/
└── page.tsx # /cart
并行路由:
app/
├── @analytics/
│ └── page.tsx
├── @team/
│ └── page.tsx
└── layout.tsx
// app/layout.tsx
export default function Layout({
children,
analytics,
team,
}: {
children: React.ReactNode
analytics: React.ReactNode
team: React.ReactNode
}) {
return (
<>
{children}
{analytics}
{team}
</>
)
}
拦截路由:
app/
├── feed/
│ └── page.tsx
├── photo/
│ └── [id]/
│ └── page.tsx
└── @modal/
└── (.)photo/
└── [id]/
└── page.tsx # Intercepts /photo/[id] when navigating from /feed
布局
布局包裹页面,并在导航过程中保留状态。
嵌套布局:
// app/layout.tsx (Root Layout)
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<header>
<nav>Global Navigation</nav>
</header>
{children}
<footer>Global Footer</footer>
</body>
</html>
)
}
// app/dashboard/layout.tsx (Dashboard Layout)
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="dashboard">
<aside>Dashboard Sidebar</aside>
<main>{children}</main>
</div>
)
}
// app/dashboard/page.tsx
export default function DashboardPage() {
return <h1>Dashboard</h1>
}
模板(导航时重新渲染):
// app/template.tsx
export default function Template({ children }: { children: React.ReactNode }) {
return (
<div>
{/* This creates a new instance on each navigation */}
{children}
</div>
)
}
加载 UI
特殊的 loading.tsx 文件可使用 React Suspense 创建加载状态。
加载状态:
// app/dashboard/loading.tsx
export default function Loading() {
return (
<div className="spinner">
<p>Loading dashboard...</p>
</div>
)
}
// app/dashboard/page.tsx
async function getData() {
const res = await fetch('https://api.example.com/data')
return res.json()
}
export default async function DashboardPage() {
const data = await getData()
return <div>{data.content}</div>
}
使用 Suspense 流式传输:
// app/page.tsx
import { Suspense } from 'react'
async function SlowComponent() {
await new Promise(resolve => setTimeout(resolve, 3000))
return <div>Slow data loaded</div>
}
export default function Page() {
return (
<div>
<h1>Page</h1>
<Suspense fallback={<div>Loading slow component...</div>}>
<SlowComponent />
</Suspense>
</div>
)
}
错误处理
特殊的 error.tsx 文件使用错误边界处理错误。
错误边界:
// app/error.tsx
'use client' // Error components must be Client Components
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<div>
<h2>Something went wrong!</h2>
<p>{error.message}</p>
<button onClick={() => reset()}>Try again</button>
</div>
)
}
全局错误:
// app/global-error.tsx
'use client'
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
return (
<html>
<body>
<h2>Application Error</h2>
<button onClick={() => reset()}>Try again</button>
</body>
</html>
)
}
未找到:
// app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
<div>
<h2>Page Not Found</h2>
<p>Could not find the requested resource</p>
<Link href="/">Return Home</Link>
</div>
)
}
API 路由
路由处理程序允许你使用 Web Request 和 Response API 创建 API 端点。
基本 API 路由:
// app/api/hello/route.ts
export async function GET(request: Request) {
return Response.json({ message: 'Hello from Next.js!' })
}
动态路由处理程序:
// app/api/posts/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const id = params.id
const post = await db.post.findUnique({ where: { id } })
if (!post) {
return new Response('Post not found', { status: 404 })
}
return Response.json(post)
}
export async function DELETE(
request: Request,
{ params }: { params: { id: string } }
) {
await db.post.delete({ where: { id: params.id } })
return new Response(null, { status: 204 })
}
带请求正文的 POST 请求:
// app/api/posts/route.ts
export async function POST(request: Request) {
const body = await request.json()
const post = await db.post.create({
data: {
title: body.title,
content: body.content,
}
})
return Response.json(post, { status: 201 })
}
带标头的请求:
// app/api/protected/route.ts
export async function GET(request: Request) {
const token = request.headers.get('authorization')
if (!token) {
return new Response('Unauthorized', { status: 401 })
}
const user = await verifyToken(token)
return Response.json({ user })
}
搜索参数:
// app/api/search/route.ts
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const query = searchParams.get('q')
const page = searchParams.get('page') || '1'
const results = await search(query, parseInt(page))
return Response.json(results)
}
CORS 标头:
// app/api/public/route.ts
export async function GET(request: Request) {
const data = { message: 'Public API' }
return Response.json(data, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
})
}
export async function OPTIONS(request: Request) {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
})
}
中间件
中间件在请求完成之前运行,可让你修改响应。
基本中间件:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Clone the request headers
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-custom-header', 'custom-value')
// Return response with modified headers
return NextResponse.next({
request: {
headers: requestHeaders,
},
})
}
export const config = {
matcher: '/api/:path*',
}
身份验证中间件:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const token = request.cookies.get('token')?.value
// Redirect to login if no token
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
// Redirect to dashboard if already logged in
if (token && request.nextUrl.pathname === '/login') {
return NextResponse.redirect(new URL('/dashboard', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/login'],
}
地理位置与重写:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const country = request.geo?.country || 'US'
// Rewrite based on country
if (country === 'GB') {
return NextResponse.rewrite(new URL('/gb' + request.nextUrl.pathname, request.url))
}
return NextResponse.next()
}
速率限制:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, '10 s'),
})
export async function middleware(request: NextRequest) {
const ip = request.ip ?? '127.0.0.1'
const { success } = await ratelimit.limit(ip)
if (!success) {
return new Response('Too Many Requests', { status: 429 })
}
return NextResponse.next()
}
export const config = {
matcher: '/api/:path*',
}
元数据和 SEO
Next.js 提供 Metadata API,用于定义页面元数据。
静态元数据:
// app/about/page.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'About Us',
description: 'Learn more about our company',
openGraph: {
title: 'About Us',
description: 'Learn more about our company',
images: ['/og-image.jpg'],
},
twitter: {
card: 'summary_large_image',
},
}
export default function AboutPage() {
return <h1>About Us</h1>
}
动态元数据:
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
type Props = {
params: { slug: string }
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage],
type: 'article',
publishedTime: post.publishedAt,
},
}
}
export default async function BlogPost({ params }: Props) {
const post = await getPost(params.slug)
return <article>{post.content}</article>
}
带图标的元数据:
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: {
default: 'My App',
template: '%s | My App',
},
description: 'My application description',
icons: {
icon: '/favicon.ico',
apple: '/apple-icon.png',
},
manifest: '/manifest.json',
}
图片优化
Next.js 使用 Image 组件自动优化图片。
基本图片:
import Image from 'next/image'
export default function Page() {
return (
<Image
src="/profile.jpg"
alt="Profile picture"
width={500}
height={500}
/>
)
}
远程图片:
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.unsplash.com',
},
],
},
}
// Component
import Image from 'next/image'
export default function Page() {
return (
<Image
src="https://images.unsplash.com/photo-1234567890"
alt="Photo"
width={800}
height={600}
priority // Load image with high priority
/>
)
}
填充容器:
import Image from 'next/image'
export default function Page() {
return (
<div style={{ position: 'relative', width: '100%', height: '400px' }}>
<Image
src="/background.jpg"
alt="Background"
fill
style={{ objectFit: 'cover' }}
/>
</div>
)
}
响应式图片:
import Image from 'next/image'
export default function Page() {
return (
<Image
src="/hero.jpg"
alt="Hero"
width={1920}
height={1080}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
)
}
字体优化
Next.js 使用 next/font 自动优化字体。
Google Fonts:
// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
})
const robotoMono = Roboto_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-roboto-mono',
})
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={`${inter.className} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
)
}
本地字体:
// app/layout.tsx
import localFont from 'next/font/local'
const myFont = localFont({
src: './fonts/my-font.woff2',
display: 'swap',
variable: '--font-my-font',
})
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className={myFont.variable}>
<body>{children}</body>
</html>
)
}
工作流模式
创建新页面
- 在
app目录中创建新文件夹 - 添加
page.tsx文件 - 导出一个默认组件
- 可选择添加布局、加载和错误文件
// app/products/page.tsx
export default function ProductsPage() {
return <h1>Products</h1>
}
// app/products/layout.tsx
export default function ProductsLayout({ children }: { children: React.ReactNode }) {
return (
<div>
<nav>Products Navigation</nav>
{children}
</div>
)
}
// app/products/loading.tsx
export default function Loading() {
return <div>Loading products...</div>
}
服务端操作
服务端操作允许你直接从客户端组件运行服务端代码。
使用服务端操作的表单:
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
await db.post.create({
data: { title, content }
})
revalidatePath('/posts')
}
// app/new-post/page.tsx
import { createPost } from '../actions'
export default function NewPostPage() {
return (
<form action={createPost}>
<input name="title" placeholder="Title" required />
<textarea name="content" placeholder="Content" required />
<button type="submit">Create Post</button>
</form>
)
}
使用 useTransition 的服务端操作:
// app/actions.ts
'use server'
export async function updateUser(userId: string, data: UserData) {
await db.user.update({
where: { id: userId },
data,
})
return { success: true }
}
// app/profile/page.tsx
'use client'
import { useTransition } from 'react'
import { updateUser } from '../actions'
export default function ProfilePage() {
const [isPending, startTransition] = useTransition()
const handleSubmit = (formData: FormData) => {
startTransition(async () => {
await updateUser('user-id', {
name: formData.get('name') as string,
})
})
}
return (
<form action={handleSubmit}>
<input name="name" />
<button disabled={isPending}>
{isPending ? 'Saving...' : 'Save'}
</button>
</form>
)
}
身份验证模式
使用中间件进行基本身份验证:
// lib/auth.ts
import { cookies } from 'next/headers'
import { redirect } from 'next/navigation'
export async function getSession() {
const session = cookies().get('session')?.value
if (!session) return null
return await verifySession(session)
}
export async function requireAuth() {
const session = await getSession()
if (!session) {
redirect('/login')
}
return session
}
// app/dashboard/page.tsx
import { requireAuth } from '@/lib/auth'
export default async function DashboardPage() {
const session = await requireAuth()
return (
<div>
<h1>Welcome, {session.user.name}</h1>
</div>
)
}
// app/api/login/route.ts
import { cookies } from 'next/headers'
export async function POST(request: Request) {
const { email, password } = await request.json()
const user = await validateCredentials(email, password)
if (!user) {
return Response.json({ error: 'Invalid credentials' }, { status: 401 })
}
const session = await createSession(user)
cookies().set('session', session, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 7, // 1 week
})
return Response.json({ success: true })
}
数据库模式
在服务端组件中使用 Prisma:
// lib/db.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = global as unknown as { prisma: PrismaClient }
export const prisma = globalForPrisma.prisma || new PrismaClient()
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
// app/posts/page.tsx
import { prisma } from '@/lib/db'
async function getPosts() {
return await prisma.post.findMany({
orderBy: { createdAt: 'desc' },
include: { author: true },
})
}
export default async function PostsPage() {
const posts = await getPosts()
return (
<div>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>By {post.author.name}</p>
<p>{post.content}</p>
</article>
))}
</div>
)
}
缓存策略
按需重新验证:
// app/actions.ts
'use server'
import { revalidatePath, revalidateTag } from 'next/cache'
export async function createPost(data: PostData) {
await db.post.create({ data })
// Revalidate specific path
revalidatePath('/posts')
// Or revalidate by tag
revalidateTag('posts')
}
// Fetch with cache tags
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { tags: ['posts'] }
})
return res.json()
}
基于时间的重新验证:
async function getData() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // Revalidate every hour
})
return res.json()
}
路由段配置:
// app/blog/page.tsx
export const revalidate = 3600 // Revalidate every hour
export const dynamic = 'force-static' // Force static generation
export const fetchCache = 'force-cache' // Force cache for all fetch requests
export default async function BlogPage() {
const posts = await getPosts()
return <div>{/* ... */}</div>
}
最佳实践
1. 默认使用服务端组件
服务端组件是默认选项,能提供更好的性能。仅在需要时使用客户端组件。
// ✅ Good - Server Component
async function getData() {
const res = await fetch('https://api.example.com/data')
return res.json()
}
export default async function Page() {
const data = await getData()
return <div>{data.title}</div>
}
// ✅ Good - Client Component only when needed
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(count + 1)}>{count}</button>
}
2. 在需要的位置获取数据
不要通过 props 层层传递数据。在需要数据的组件中获取数据。
// ✅ Good - Fetch where needed
async function Header() {
const user = await getUser()
return <div>Welcome, {user.name}</div>
}
async function Posts() {
const posts = await getPosts()
return <div>{/* render posts */}</div>
}
export default function Page() {
return (
<>
<Header />
<Posts />
</>
)
}
3. 并行获取数据
尽可能并行获取数据,以缩短加载时间。
// ✅ Good - Parallel fetching
export default async function Page() {
const [user, posts] = await Promise.all([
getUser(),
getPosts()
])
return <div>{/* ... */}</div>
}
4. 优化图片
始终使用 Image 组件进行自动优化。
// ✅ Good
import Image from 'next/image'
<Image
src="/hero.jpg"
alt="Hero"
width={1920}
height={1080}
priority
/>
// ❌ Bad
<img src="/hero.jpg" alt="Hero" />
5. 使用 Metadata API 优化 SEO
为每个页面定义元数据。
// ✅ Good
export const metadata = {
title: 'My Page',
description: 'Page description',
}
export default function Page() {
return <div>Content</div>
}
6. 实现错误边界
添加 error.tsx 文件以处理错误。
// app/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>
)
}
7. 使用加载状态
添加 loading.tsx 文件以显示加载界面。
// app/loading.tsx
export default function Loading() {
return <div>Loading...</div>
}
8. 使用路由处理程序实现 API
使用路由处理程序,而不是 pages 目录中的 API 路由。
// ✅ Good - app/api/users/route.ts
export async function GET(request: Request) {
const users = await db.user.findMany()
return Response.json(users)
}
9. 使用中间件处理通用逻辑
在中间件中实现身份验证、重定向和重写。
// middleware.ts
export function middleware(request: NextRequest) {
const token = request.cookies.get('token')
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
10. 优化字体
使用 next/font 自动优化字体。
// ✅ Good
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
export default function RootLayout({ children }) {
return (
<html className={inter.className}>
<body>{children}</body>
</html>
)
}
常见模式
博客模式
// app/blog/page.tsx
import Link from 'next/link'
async function getPosts() {
const res = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }
})
return res.json()
}
export default async function BlogPage() {
const posts = await getPosts()
return (
<div>
<h1>Blog</h1>
{posts.map((post) => (
<article key={post.id}>
<h2>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
)
}
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`)
if (!res.ok) return null
return res.json()
}
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json())
return posts.map((post) => ({ slug: post.slug }))
}
export async function generateMetadata({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
if (!post) return {}
return {
title: post.title,
description: post.excerpt,
}
}
export default async function BlogPostPage({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
if (!post) {
notFound()
}
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}
电子商务模式
// app/products/page.tsx
import { Suspense } from 'react'
import ProductGrid from './ProductGrid'
import Filters from './Filters'
export default function ProductsPage({
searchParams,
}: {
searchParams: { category?: string; sort?: string }
}) {
return (
<div>
<h1>Products</h1>
<Filters />
<Suspense fallback={<div>Loading products...</div>}>
<ProductGrid searchParams={searchParams} />
</Suspense>
</div>
)
}
// app/products/ProductGrid.tsx
async function getProducts(category?: string, sort?: string) {
const params = new URLSearchParams()
if (category) params.set('category', category)
if (sort) params.set('sort', sort)
const res = await fetch(`https://api.example.com/products?${params}`, {
next: { revalidate: 300 }
})
return res.json()
}
export default async function ProductGrid({
searchParams,
}: {
searchParams: { category?: string; sort?: string }
}) {
const products = await getProducts(searchParams.category, searchParams.sort)
return (
<div className="grid">
{products.map((product) => (
<ProductCard key={product.id} product={product} />
))}
</div>
)
}
// app/products/[id]/page.tsx
async function getProduct(id: string) {
const res = await fetch(`https://api.example.com/products/${id}`)
return res.json()
}
export default async function ProductPage({ params }: { params: { id: string } }) {
const product = await getProduct(params.id)
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>${product.price}</p>
<AddToCartButton productId={product.id} />
</div>
)
}
仪表盘模式
// app/dashboard/layout.tsx
import { requireAuth } from '@/lib/auth'
import Sidebar from './Sidebar'
export default async function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
const session = await requireAuth()
return (
<div className="dashboard">
<Sidebar user={session.user} />
<main>{children}</main>
</div>
)
}
// app/dashboard/page.tsx
import { Suspense } from 'react'
import Stats from './Stats'
import RecentActivity from './RecentActivity'
import Chart from './Chart'
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<Suspense fallback={<div>Loading stats...</div>}>
<Stats />
</Suspense>
<div className="grid">
<Suspense fallback={<div>Loading chart...</div>}>
<Chart />
</Suspense>
<Suspense fallback={<div>Loading activity...</div>}>
<RecentActivity />
</Suspense>
</div>
</div>
)
}
总结
此 Next.js 开发技能涵盖:
- App Router:基于文件系统的现代路由
- 服务端组件:默认在服务端渲染,以获得更好的性能
- 数据获取:扩展的 fetch API,支持缓存和重新验证
- 路由:动态路由、全匹配路由、路由组、并行路由
- 布局:嵌套布局、模板和布局组合
- 加载状态:使用 Suspense 自动显示加载界面
- 错误处理:错误边界和未找到页面
- API 路由:使用 Web API 的路由处理程序
- 中间件:拦截和修改请求
- 元数据:使用 Metadata API 优化 SEO
- 图片优化:使用 Image 组件自动优化图片
- 字体优化:使用 next/font 自动加载字体
- 服务端操作:从客户端组件执行服务端变更
- 身份验证:会话管理和受保护路由
- 最佳实践:性能优化、缓存策略和常见模式
所有模式均基于 Next.js 官方文档(信任评分:10),代表了现代 Next.js 13+ App Router 开发实践。