乐于分享
好东西不私藏

Next.js 14 App Router 最佳实践

Next.js 14 App Router 最佳实践
  • • 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(formDataFormData) {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(requestNextRequest) {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. 1. 过度使用 'use client':导致 bundle 增大,仅在需要交互、状态或浏览器 API 时使用
  2. 2. Server Actions 滥用:适合表单提交和数据变更,不适合复杂状态管理和实时更新
  3. 3. 缓存理解不足:需要清楚 Full Route Cache、Data Cache、Router Cache、Request Memoization 四层缓存
  4. 4. 中间件滥用:Edge Runtime 有运行时限制(不支持 Node.js API)
  5. 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',});// 零布局偏移,自动内联关键 CSS

6.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-analyzer

7. 数据变更模式

7.1 Server Actions + revalidatePath

// app/actions.ts'use server';import { revalidatePath, revalidateTag } from'next/cache';exportasyncfunctionupdatePost(idstringdataFormData) {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

更多详细内容,请微信搜索“前端爱好者, 戳我 查看 。