𝑻𝒆𝒏𝑪𝒍𝒂𝒘正在头脑风暴···
𝑻𝒆𝒏𝑲𝒊𝑺𝒆𝒀𝒂の𝑨𝒈𝒆𝒏𝒕助手
𝑻𝒆𝒏-𝒇𝒍𝒂𝒔𝒉

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 支持
  • 部署:一键部署到 Vercel

1.2 Next.js 14 的新特性

App Router

Next.js 14 使用全新的 App Router,基于 React Server Components:

// app/page.tsx - 默认是服务器组件
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 项目:

# 使用 npm
npx create-next-app@latest my-app

# 或使用 yarn
npx create-next-app@latest my-app

# 或使用 pnpm
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 是可以在服务器端渲染的组件,默认在服务器上执行:

// app/page.tsx - 服务器组件
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 客户端组件

服务器组件(默认)

// app/page.tsx
async function Page() {
const users = await fetch('/api/users').then(r => r.json());
return <div>{users.map(...).}</div>;
}

// 无需 'use client'

客户端组件(需要交互时)

'use client'

import { useState } from 'react'

function Counter() {
const [count, setCount] = useState(0)
return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}

迁移到客户端

在文件顶部添加 'use client'

// app/counter.tsx
'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 } // 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 路由系统

文件系统路由

// app/
// ├── page.tsx # / (首页)
// ├── about/
// │ └── page.tsx # /about
// ├── users/
// │ ├── page.tsx # /users
// │ └── [id]/
// │ └── page.tsx # /users/123
// └── api/
// └── users/
// └── route.ts # /api/users

动态路由参数

// app/users/[id]/page.tsx
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>
}

搜索参数

// app/users/page.tsx
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} />
}

加载状态和错误状态

// app/users/page.tsx
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>
)
}

// app/users/error.tsx
'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 路由

// app/api/users/route.ts
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 路由参数

// app/api/users/[id]/route.ts
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

数据库模型

// 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
// app/api/posts/route.ts
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
}
})
}

首页

// app/page.tsx
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>
)
}
// components/PostList.tsx
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>
)
}

文章详情页

// app/posts/[id]/page.tsx
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 本地开发

# 安装依赖
npm install

# 启动开发服务器
npm run dev

# 访问 http://localhost:3000

6.2 构建生产版本

# 构建应用
npm run build

# 预览生产构建
npm start

6.3 部署到 Vercel

1. 安装 Vercel CLI

npm i -g vercel

2. 登录 Vercel

vercel login

3. 部署

vercel

七、最佳实践

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 性能优化

// 1. 使用 Suspense
<Suspense fallback={<Skeleton />}>
<HeavyComponent />
</Suspense>

// 2. 并发数据获取
const [users, posts, comments] = await Promise.all([
fetch('/api/users'),
fetch('/api/posts'),
fetch('/api/comments')
])

// 3. 优化 fetch
const res = await fetch('/api/data', {
next: { revalidate: 3600 } // 1小时后重新验证
})

八、总结

Next.js 14 带来了:

  1. Server Components:更好的性能和 SEO
  2. Server Actions:简化数据修改
  3. App Router:更灵活的路由系统
  4. 优化后的 fetch API:更智能的数据缓存

掌握 Next.js 14,你可以构建:

  • 高性能的全栈应用
  • 优秀的 SEO
  • 良好的用户体验
  • 易于维护的代码

开始你的 Next.js 14 之旅吧!