• Next.js 14 App Router 最佳实践 • 1. 问题概述 • 2. 核心概念 • 2.1 Server Components vs Client Components • 2.2 Server Actions • 3. 最佳实践 • 3.1 1. 数据获取 • 3.2 2. 组件拆分策略 • 3.3 3. 流式渲染 • 3.4 4. 缓存策略 • 4. 路由系统深入 • 4.1 路由分组与布局 • 4.2 并行路由与拦截路由 • 4.3 中间件实战 • 5. 常见陷阱 • 6. 性能优化 • 6.1 图片优化 • 6.2 字体优化 • 6.3 Partial Prerendering (PPR) • 6.4 Bundle 分析 • 7. 数据变更模式 • 7.1 Server Actions + revalidatePath • 7.2 乐观更新 • 8. 参考资源
⚠️ AI生成 · 内容仅供参考 — 本文由AI自动生成,内容可能存在不准确之处,请以官方文档和实际实践为准。

Next.js 14 App Router 最佳实践
1. 问题概述
Next.js 14 的 App Router 已成为 React 全栈开发的主流范式。Server Components、Server Actions、流式渲染等新概念让很多开发者面临学习曲线,如何正确使用 App Router 是当前热门话题。
2. 核心概念
2.1 Server Components vs Client Components
┌─────────────────────────────────────────┐│ 页面组件 ││ ┌─────────────────────────────────┐ ││ │ Server Component(默认) │ ││ │ - 直接访问数据库 │ ││ │ - 零客户端 JS │ ││ └─────────────────────────────────┘ ││ ┌─────────────────────────────────┐ ││ │ Client Component │ ││ │ - 'use client' │ ││ │ - 交互、状态、浏览器 API │ ││ └─────────────────────────────────┘ │└─────────────────────────────────────────┘2.2 Server Actions
// app/actions.ts'use server'import { revalidatePath } from'next/cache'exportasyncfunctioncreatePost(formData: FormData) {const title = formData.get('title')await db.post.create({ data: { title } })revalidatePath('/posts')}3. 最佳实践
3.1 1. 数据获取
// ✅ 推荐:在 Server Component 中直接获取asyncfunctionPostList() {const posts = await db.post.findMany()return posts.map(post =><PostCardkey={post.id}post={post} />)}// ❌ 避免:在 Client Component 中直接请求数据库3.2 2. 组件拆分策略
页面├── Server Components(数据获取、静态内容)│ ├── Client Components(交互部分)│ └── Server Components(更多静态内容)└── Client Components(包装交互状态)3.3 3. 流式渲染
import { Suspense } from'react'exportdefaultfunctionPage() {return (<div><h1>Dashboard</h1><Suspensefallback={<Skeleton />}><SlowComponent /></Suspense></div> )}3.4 4. 缓存策略
// 静态渲染(默认)// 动态渲染exportconst dynamic = 'force-dynamic'// 重新验证exportconst revalidate = 3600// 1小时4. 路由系统深入
4.1 路由分组与布局
// app/(marketing)/layout.tsx - 营销页面共享布局exportdefaultfunctionMarketingLayout({ children }) {return (<div><MarketingNav /> {children}<MarketingFooter /></div> );}// app/(dashboard)/layout.tsx - 后台页面共享布局exportdefaultfunctionDashboardLayout({ children }) {return (<divclassName="flex"><Sidebar /><mainclassName="flex-1">{children}</main></div> );}4.2 并行路由与拦截路由
// app/layout.tsxexportdefaultfunctionLayout({ children, modal }) {return (<div> {children} {modal} {/* 并行渲染的 modal */}</div> );}// app/@modal/(.)photo/[id]/page.tsx - 拦截路由exportdefaultfunctionPhotoModal({ params }) {return (<Modal><Photoid={params.id} /></Modal> );}4.3 中间件实战
// middleware.tsimport { NextResponse } from'next/server';importtype { NextRequest } from'next/server';exportfunctionmiddleware(request: NextRequest) {const token = request.cookies.get('token');const { pathname } = request.nextUrl;// 路由保护if (pathname.startsWith('/dashboard') && !token) {returnNextResponse.redirect(newURL('/login', request.url)); }// 国际化路由if (pathname === '/') {const locale = request.headers.get('accept-language')?.split(',')[0] || 'en';returnNextResponse.redirect(newURL(`/${locale}`, request.url)); }returnNextResponse.next();}exportconst config = {matcher: ['/dashboard/:path*', '/'],};5. 常见陷阱
1. 过度使用 'use client':导致 bundle 增大,仅在需要交互、状态或浏览器 API 时使用 2. Server Actions 滥用:适合表单提交和数据变更,不适合复杂状态管理和实时更新 3. 缓存理解不足:需要清楚 Full Route Cache、Data Cache、Router Cache、Request Memoization 四层缓存 4. 中间件滥用:Edge Runtime 有运行时限制(不支持 Node.js API) 5. 环境变量泄露:客户端变量必须 NEXT_PUBLIC_前缀,敏感信息仅在服务端
6. 性能优化
6.1 图片优化
importImagefrom'next/image';// 自动优化:WebP/AVIF 格式、懒加载、尺寸优化<Imagesrc="/hero.jpg"alt="Hero"width={1200}height={600}priority // 首屏图片预加载placeholder="blur" // 模糊占位/>6.2 字体优化
import { Inter } from'next/font/google';const inter = Inter({subsets: ['latin'],display: 'swap',variable: '--font-inter',});// 零布局偏移,自动内联关键 CSS6.3 Partial Prerendering (PPR)
// 静态外壳 + 动态内容流式注入exportconst experimental_ppr = true;exportdefaultfunctionPage() {return (<div><StaticShell /> {/* 构建时预渲染 */}<Suspensefallback={<Skeleton />}><DynamicContent /> {/* 请求时动态渲染 */}</Suspense></div> );}6.4 Bundle 分析
# 分析打包体积ANALYZE=true next build# 查看模块依赖npx next-bundle-analyzer7. 数据变更模式
7.1 Server Actions + revalidatePath
// app/actions.ts'use server';import { revalidatePath, revalidateTag } from'next/cache';exportasyncfunctionupdatePost(id: string, data: FormData) {await db.post.update({ where: { id }, data: { title: data.get('title') } });revalidatePath('/posts');revalidateTag(`post-${id}`);}7.2 乐观更新
'use client';import { useOptimistic } from'react';import { updatePost } from'./actions';functionPostEditor({ post }) {const [optimisticPost, setOptimistic] = useOptimistic( post,(state, newTitle) => ({ ...state, title: newTitle }) );asyncfunctionhandleSubmit(formData) {const title = formData.get('title');setOptimistic(title); // 立即更新 UIawaitupdatePost(post.id, formData); // 服务端更新 }return (<formaction={handleSubmit}><h2>{optimisticPost.title}</h2><inputname="title"defaultValue={post.title} /><buttontype="submit">保存</button></form> );}8. 参考资源
• Next.js 官方文档 • Next.js App Router 课程 • Vercel Templates
更多详细内容,请微信搜索“前端爱好者“, 戳我 查看 。
夜雨聆风