Next.js 14 全栈开发实战 Next.js 14 是 React 框架的里程碑式更新,带来了许多革命性的新特性。通过本文,你将全面掌握 Next.js 14 的核心概念和实战技能,从零开始构建高性能、现代化的全栈应用。
一、Next.js 14 概述 1.1 什么是 Next.js? Next.js 是基于 React 的全栈框架,提供开箱即用的功能,包括:
服务端渲染 (SSR) :提升首屏加载性能静态站点生成 (SSG) :优化 SEO 和性能路由系统 :支持文件系统路由API 路由 :构建全栈应用数据获取 :服务端数据缓存策略国际化 :i18n 支持部署 :一键部署到 Vercel1.2 Next.js 14 的新特性 App Router Next.js 14 使用全新的 App Router,基于 React Server Components:
async function Page ( ) { const data = await fetch ('https://api.example.com/users' ); const users = await data.json (); return ( <div > <h1 > 用户列表</h1 > {users.map(user => ( <div key ={user.id} > {user.name}</div > ))} </div > ); } export default Page ;
Server Actions 直接在组件中调用服务器函数:
'use server' export async function createPost (formData: FormData ) { const title = formData.get ('title' ) as string; }
并发数据获取 使用 Promise.all 并发获取多个数据:
async function Page ( ) { const [users, posts] = await Promise .all ([ fetch ('/api/users' ), fetch ('/api/posts' ) ]); const usersData = await users.json (); const postsData = await posts.json (); return ( <div > <h1 > 用户</h1 > {usersData.map(user => <div key ={user.id} > {user.name}</div > )} <h1 > 文章</h1 > {postsData.map(post => <div key ={post.id} > {post.title}</div > )} </div > ); }
二、安装和初始化 2.1 创建新项目 使用官方 CLI 创建 Next.js 项目:
npx create-next-app@latest my-app npx create-next-app@latest my-app pnpm create next-app my-app
初始化过程中会询问以下问题:
✔ What is your project named? › my-app ✔ Would you like to use TypeScript? … No / Yes ✔ Would you like to use ESLint? … No / Yes ✔ Would you like to use Tailwind CSS? … No / Yes ✔ Would you like to use `src/` directory? … No / Yes ✔ Would you like to use App Router? … No / Yes ✔ Would you like to customize the default import alias ? … No / Yes
2.2 项目结构 Next.js 14 的项目结构:
my-app/ ├── app/ # App Router 目录 │ ├── layout.tsx # 根布局 │ ├── page.tsx # 首页 │ ├── globals.css # 全局样式 │ └── api/ # API 路由 │ └── users/ │ └── route.ts ├── public / # 静态资源 ├── package.json ├── tsconfig.json └── next .config.js
三、核心概念 3.1 React Server Components (RSC) 什么是 RSC RSC 是可以在服务器端渲染的组件,默认在服务器上执行:
async function Page ( ) { const res = await fetch ('https://api.example.com/users' ); const users = await res.json (); return ( <div > <h1 > 用户列表</h1 > {users.map(user => ( <div key ={user.id} > {user.name}</div > ))} </div > ); }
服务器组件 vs 客户端组件 服务器组件(默认) :
async function Page ( ) { const users = await fetch ('/api/users' ).then (r => r.json ()); return <div > {users.map(...).}</div > ; }
客户端组件(需要交互时) :
'use client' import { useState } from 'react' function Counter ( ) { const [count, setCount] = useState (0 ) return <button onClick ={() => setCount(c => c + 1)}>{count}</button > }
迁移到客户端 在文件顶部添加 'use client':
'use client' import { useState } from 'react' export default function Counter ( ) { const [count, setCount] = useState (0 ) return ( <div > <p > 计数: {count}</p > <button onClick ={() => setCount(c => c + 1)}>增加</button > </div > ) }
3.2 数据获取 fetch API Next.js 优化了 fetch API:
const res = await fetch ('https://api.example.com/users' )const users = await res.json ()const res = await fetch ('https://api.example.com/users' , { cache : 'no-store' }) const res = await fetch ('https://api.example.com/users' , { next : { revalidate : 60 } }) const res = await fetch ('https://api.example.com/users' , { cache : 'force-cache' , next : { revalidate : 0 } })
并发数据获取 async function Page ( ) { const [users, posts, comments] = await Promise .all ([ fetch ('/api/users' ).then (r => r.json ()), fetch ('/api/posts' ).then (r => r.json ()), fetch ('/api/comments' ).then (r => r.json ()) ]) return ( <div > <h1 > 用户</h1 > {users.map(user => <UserCard key ={user.id} user ={user} /> )} <h1 > 文章</h1 > {posts.map(post => <PostCard key ={post.id} post ={post} /> )} </div > ) }
错误处理 async function Page ( ) { try { const res = await fetch ('/api/users' ) if (!res.ok ) { throw new Error ('获取用户失败' ) } const users = await res.json () return <UserList users ={users} /> } catch (error) { return <Error message ={error.message} /> } }
3.3 路由系统 文件系统路由 动态路由参数 export default async function UserPage ({ params }: { params: { id: string } } ) { const res = await fetch (`/api/users/${params.id} ` ) const user = await res.json () return <div > {user.name}</div > }
搜索参数 export default async function UsersPage ({ searchParams }: { searchParams: { page?: string; keyword?: string } } ) { const page = searchParams.page ? parseInt (searchParams.page ) : 1 const keyword = searchParams.keyword || '' const res = await fetch (`/api/users?page=${page} &keyword=${keyword} ` ) const users = await res.json () return <UserList users ={users} /> }
加载状态和错误状态 import { Suspense } from 'react' function UsersList ({ page, keyword }: { page: number; keyword: string } ) { const res = await fetch (`/api/users?page=${page} &keyword=${keyword} ` ) const users = await res.json () return <ul > {users.map(user => <li key ={user.id} > {user.name}</li > )}</ul > } export default async function UsersPage ({ searchParams }: { searchParams: any } ) { return ( <div > <Suspense fallback ={ <p > 加载中...</p > }> <UsersList page ={parseInt(searchParams.page || 1 )} keyword ={searchParams.keyword || ''} /> </Suspense > </div > ) } 'use client' export default function Error ({ error }: { error: Error } ) { return <p > 错误: {error.message}</p > }
3.4 Server Actions 创建 Server Action 'use server' import { db } from '@/lib/db' export async function createPost (formData: FormData ) { const title = formData.get ('title' ) as string const content = formData.get ('content' ) as string const userId = formData.get ('userId' ) as string await db.post .create ({ data : { title, content, authorId : userId } }) }
在组件中使用 Server Action import { createPost } from '@/app/actions' export default function CreatePostForm ( ) { async function handleSubmit (formData: FormData ) { 'use server' await createPost (formData) } return ( <form action ={handleSubmit} > <input name ="title" placeholder ="标题" required /> <textarea name ="content" placeholder ="内容" required /> <input name ="userId" type ="hidden" value ="123" /> <button type ="submit" > 发布</button > </form > ) }
在客户端组件中使用 'use client' import { createPost } from '@/app/actions' export default function CreatePostForm ( ) { const handleSubmit = async (e: React.FormEvent<HTMLFormElement> ) => { e.preventDefault () const formData = new FormData (e.currentTarget ) await createPost (formData) } return ( <form onSubmit ={handleSubmit} > <input name ="title" placeholder ="标题" required /> <textarea name ="content" placeholder ="内容" required /> <button type ="submit" > 发布</button > </form > ) }
四、API 路由 4.1 创建 API 路由 import { NextResponse } from 'next/server' import { db } from '@/lib/db' export async function GET (request: Request ) { const { searchParams } = new URL (request.url ) const page = parseInt (searchParams.get ('page' ) || '1' ) const keyword = searchParams.get ('keyword' ) || '' try { const users = await db.user .findMany ({ skip : (page - 1 ) * 10 , take : 10 , where : keyword ? { name : { contains : keyword } } : undefined }) return NextResponse .json (users) } catch (error) { return NextResponse .json ( { error : '获取用户失败' }, { status : 500 } ) } } export async function POST (request: Request ) { const body = await request.json () try { const user = await db.user .create ({ data : body }) return NextResponse .json (user, { status : 201 }) } catch (error) { return NextResponse .json ( { error : '创建用户失败' }, { status : 500 } ) } }
4.2 API 路由参数 import { NextResponse } from 'next/server' import { db } from '@/lib/db' export async function GET ( request: Request, { params }: { params: { id: string } } ) { const user = await db.user .findUnique ({ where : { id : params.id } }) if (!user) { return NextResponse .json ( { error : '用户不存在' }, { status : 404 } ) } return NextResponse .json (user) } export async function PATCH ( request: Request, { params }: { params: { id: string } } ) { const body = await request.json () const user = await db.user .update ({ where : { id : params.id }, data : body }) return NextResponse .json (user) } export async function DELETE ( request: Request, { params }: { params: { id: string } } ) { await db.user .delete ({ where : { id : params.id } }) return NextResponse .json ({ message : '删除成功' }) }
五、实战项目 5.1 创建博客应用 项目结构 blog/ ├── app/ │ ├── api/ │ │ ├── posts/ │ │ │ └── route.ts │ │ └── users/ │ │ └── route.ts │ ├── posts/ │ │ ├── [id] / │ │ │ └── page.tsx │ │ └── page.tsx │ ├── layout.tsx │ └── page.tsx ├── components/ │ ├── PostCard.tsx │ ├── PostForm.tsx │ └── UserList.tsx └── lib/ └── db.ts
数据库模型 import { PrismaClient } from '@prisma/client' const globalForPrisma = global as unknown as { prisma : PrismaClient }export const db = globalForPrisma.prisma || new PrismaClient ()if (process.env .NODE_ENV !== 'production' ) globalForPrisma.prisma = db
import { NextResponse } from 'next/server' import { db } from '@/lib/db' export async function GET (request: Request ) { const { searchParams } = new URL (request.url ) const page = parseInt (searchParams.get ('page' ) || '1' ) const limit = parseInt (searchParams.get ('limit' ) || '10' ) try { const posts = await db.post .findMany ({ skip : (page - 1 ) * limit, take : limit, include : { author : true }, orderBy : { createdAt : 'desc' } }) return NextResponse .json (posts) } catch (error) { return NextResponse .json ( { error : '获取文章失败' }, { status : 500 } ) } } 'use server' export async function createPost (formData: FormData ) { const title = formData.get ('title' ) as string const content = formData.get ('content' ) as string const authorId = formData.get ('authorId' ) as string await db.post .create ({ data : { title, content, authorId } }) }
首页 import { Suspense } from 'react' import { PostList } from '@/components/PostList' export default async function Home ( ) { return ( <div > <h1 > 我的博客</h1 > <Suspense fallback ={ <p > 加载文章中...</p > }> <PostList /> </Suspense > </div > ) }
import { getPosts } from '@/lib/api/posts' export async function PostList ( ) { const posts = await getPosts () return ( <div > {posts.map(post => ( <PostCard key ={post.id} post ={post} /> ))} </div > ) }
文章详情页 import { db } from '@/lib/db' export default async function PostPage ({ params }: { params: { id: string } } ) { const post = await db.post .findUnique ({ where : { id : params.id }, include : { author : true , comments : { include : { author : true } } } }) if (!post) { return <div > 文章不存在</div > } return ( <article > <h1 > {post.title}</h1 > <div > 作者: {post.author.name}</div > <p > {post.content}</p > <hr /> <h2 > 评论</h2 > {post.comments.map(comment => ( <div key ={comment.id} > <div > {comment.author.name}</div > <p > {comment.content}</p > </div > ))} </article > ) }
六、部署 6.1 本地开发 6.2 构建生产版本 6.3 部署到 Vercel 1. 安装 Vercel CLI 2. 登录 Vercel 3. 部署 七、最佳实践 7.1 代码组织 app/ ├── users/ │ ├── [id]/ │ │ └── page.tsx # 用户详情页 │ ├── page.tsx # 用户列表页 │ └── edit/ │ └── page.tsx # 编辑页面 ├── posts/ │ ├── [id]/ │ │ └── page.tsx │ └── page.tsx ├── api/ │ ├── users/ │ │ ├── route.ts # GET /api/users │ │ └── [id]/ │ │ └── route.ts # GET /api/users/:id
7.2 错误处理 async function Page ( ) { try { const res = await fetch ('/api/data' ) if (!res.ok ) { throw new Error (`HTTP error! status: ${res.status} ` ) } const data = await res.json () return <DataView data ={data} /> } catch (error) { return <ErrorView error ={error} /> } }
7.3 性能优化 <Suspense fallback={<Skeleton /> }> <HeavyComponent /> </Suspense > const [users, posts, comments] = await Promise .all ([ fetch ('/api/users' ), fetch ('/api/posts' ), fetch ('/api/comments' ) ]) const res = await fetch ('/api/data' , { next : { revalidate : 3600 } })
八、总结 Next.js 14 带来了:
Server Components :更好的性能和 SEOServer Actions :简化数据修改App Router :更灵活的路由系统优化后的 fetch API :更智能的数据缓存掌握 Next.js 14,你可以构建:
高性能的全栈应用 优秀的 SEO 良好的用户体验 易于维护的代码 开始你的 Next.js 14 之旅吧!