Next.js
next.js是一个基于React的通用JavaScript框架,next.js为React组件模型提供了扩展,支持基于服务器的组件渲染,同时也支持在客户端继续进行渲染
next.js是一个基于React的通用JavaScript框架,next.js为React组件模型提供了扩展,支持基于服务器的组件渲染,同时也支持在客户端继续进行渲染
next.js是一个基于React的通用JavaScript框架,next.js为React组件模型提供了扩展,支持基于服务器的组件渲染,同时也支持在客户端继续进行渲染

查看更多相关内容
Vercel 与 Next.js 的集成优势是什么?## Vercel 与 Next.js 的集成优势是什么?
Vercel 与 Next.js 的集成可以说是天作之合,因为 Vercel 的创始团队也是 Next.js 的创建者。这种深度集成带来了许多独特的优势,使得在 Vercel 上部署 Next.js 应用成为最佳选择。
### 深度集成的技术优势
#### 1. 零配置部署
**自动检测和优化**:
- Vercel 自动识别 Next.js 项目
- 自动配置构建设置和路由
- 无需手动配置 `vercel.json`
- 自动应用 Next.js 特定的优化
**智能构建**:
- 自动识别页面类型(静态、动态、ISR)
- 优化构建流程
- 自动处理图片优化
- 智能缓存策略
#### 2. Serverless Functions 无缝支持
**API Routes 部署**:
```javascript
// pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({ message: 'Hello from Vercel' });
}
```
**自动部署为 Serverless 函数**:
- 每个 API Route 自动成为独立的 Serverless 函数
- 自动处理函数的冷启动
- 优化的函数内存和超时配置
- 自动扩展以应对流量
#### 3. 增量静态再生成(ISR)优化
**原生支持**:
```javascript
export async function getStaticProps() {
return {
props: { data: await fetchData() },
revalidate: 60 // 每 60 秒重新生成
};
}
```
**Vercel 特定优化**:
- 智能的缓存失效策略
- 后台重新生成,不影响用户体验
- 分布式缓存确保一致性
- 自动处理 CDN 缓存
#### 4. 边缘运行时支持
**Edge Runtime**:
```javascript
export const runtime = 'edge';
export default function handler() {
return new Response('Hello from Edge!');
}
```
**优势**:
- 在全球边缘节点执行代码
- 极低的延迟
- 自动地理位置路由
- 优化的冷启动时间
### 性能优化
#### 1. 图片优化
**自动图片优化**:
```jsx
import Image from 'next/image';
<Image
src="/hero.jpg"
alt="Hero"
width={800}
height={600}
priority
/>
```
**Vercel 优化**:
- 自动生成多种尺寸和格式
- WebP、AVIF 等现代格式支持
- 智能的懒加载
- CDN 缓存优化后的图片
#### 2. 字体优化
**next/font 集成**:
```jsx
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'] });
```
**优势**:
- 自动优化字体加载
- 零布局偏移
- 自动托管字体文件
- 智能的字体子集化
#### 3. 代码分割和懒加载
**自动优化**:
- 路由级别的代码分割
- 组件级别的懒加载
- 自动预加载关键资源
- 优化的包大小
### 开发体验提升
#### 1. 预览部署
**Pull Request 预览**:
- 每个 PR 自动生成预览 URL
- 实时更新预览
- 独立的环境变量
- 便于代码审查
#### 2. 实时日志
**详细的日志信息**:
- 构建日志
- 运行时日志
- 错误堆栈跟踪
- 性能指标
#### 3. Analytics 集成
**Vercel Analytics**:
```jsx
import { Analytics } from '@vercel/analytics/react';
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Analytics />
</body>
</html>
);
}
```
**功能**:
- Web Vitals 监控
- 用户行为分析
- 性能洞察
- 无需额外配置
### 高级功能支持
#### 1. 中间件支持
**Next.js Middleware**:
```javascript
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
return NextResponse.rewrite(new URL('/dashboard', request.url));
}
export const config = {
matcher: '/home/:path*',
};
```
**Vercel 优势**:
- 在边缘运行中间件
- 极快的响应时间
- 自动扩展
- 支持复杂的路由逻辑
#### 2. Server Components 支持
**React Server Components**:
```jsx
// Server Component
async function UserProfile({ userId }) {
const user = await fetchUser(userId);
return <div>{user.name}</div>;
}
```
**优势**:
- 自动在服务器端渲染
- 减少客户端 JavaScript
- 更快的首屏加载
- 更好的 SEO
#### 3. Streaming 支持
**渐进式渲染**:
```jsx
import { Suspense } from 'react';
export default function Page() {
return (
<div>
<Header />
<Suspense fallback={<Loading />}>
<SlowComponent />
</Suspense>
</div>
);
}
```
**Vercel 优化**:
- 自动支持 Streaming
- 优化的传输协议
- 更快的 Time to First Byte (TTFB)
- 更好的用户体验
### 部署和运维优势
#### 1. 自动扩展
**按需扩展**:
- 自动处理流量峰值
- 无需手动配置服务器
- 全球边缘网络
- 高可用性保证
#### 2. 回滚功能
**一键回滚**:
- 保留所有历史部署
- 快速回滚到任何版本
- 零停机时间
- 简单的版本管理
#### 3. 环境管理
**多环境支持**:
- Production、Preview、Development 环境
- 独立的环境变量
- 环境特定的配置
- 简化的环境切换
### 成本效益
#### 1. 免费额度
**免费计划包含**:
- 无限带宽
- 100GB 带宽
- 6,000 分钟构建时间
- 100GB-Hours Serverless Functions
- 无限预览部署
#### 2. 按需付费
**付费计划优势**:
- 更高的配额
- 优先支持
- 团队协作功能
- 高级分析
### 与其他平台的对比
#### 1. vs Netlify
**Vercel 优势**:
- 更好的 Next.js 支持
- 更快的边缘函数
- 更详细的日志
- 更好的开发体验
#### 2. vs AWS Amplify
**Vercel 优势**:
- 更简单的配置
- 更快的部署
- 更好的预览部署
- 更直观的界面
#### 3. vs 自托管
**Vercel 优势**:
- 零运维成本
- 自动扩展
- 全球 CDN
- 自动 SSL
### 最佳实践
#### 1. 利用 ISR
- 对动态内容使用 ISR
- 设置合理的 revalidate 时间
- 使用 on-demand revalidation
- 监控缓存命中率
#### 2. 优化图片
- 使用 next/image 组件
- 提供正确的尺寸
- 使用 priority 属性
- 启用自动格式转换
#### 3. 使用 Edge Runtime
- 对需要低延迟的功能使用 Edge Runtime
- 注意 Edge Runtime 的限制
- 合理划分 Serverless 和 Edge 函数
#### 4. 监控性能
- 使用 Vercel Analytics
- 监控 Web Vitals
- 跟踪错误率
- 优化关键路径
### 实际应用案例
#### 1. 电商网站
**优势**:
- ISR 实现产品页面缓存
- Edge Functions 处理购物车
- 图片优化提升加载速度
- 全球 CDN 确保快速访问
#### 2. 内容平台
**优势**:
- SSG 生成静态页面
- ISR 更新内容
- 预览部署便于内容审核
- Analytics 了解用户行为
#### 3. SaaS 应用
**优势**:
- Serverless Functions 处理 API
- Middleware 处理认证
- Edge Runtime 提升响应速度
- 自动扩展应对用户增长
### 总结
Vercel 与 Next.js 的深度集成提供了:
1. **零配置体验**:自动识别和优化
2. **卓越性能**:边缘网络、CDN、优化
3. **开发效率**:预览部署、实时日志
4. **可扩展性**:自动扩展、高可用
5. **成本效益**:免费额度、按需付费
这种集成使得开发者能够专注于构建功能,而不必担心基础设施和部署细节,是 Next.js 应用的理想部署平台。
服务端 · 2月21日 16:50
Next.js 13+ 的 React Server Components 是什么?Next.js 13 引入了 React Server Components(RSC),这是一个重大的架构变革,彻底改变了我们在 Next.js 中构建应用的方式。
## 什么是 React Server Components?
React Server Components 是一种新的组件类型,它们在服务器上渲染,而不是在客户端。这意味着:
1. **服务器端渲染**:组件在服务器上执行,生成 HTML
2. **零客户端 JavaScript**:服务器组件不会发送任何 JavaScript 到客户端
3. **直接访问后端资源**:可以直接访问数据库、文件系统等
4. **保持代码私密**:服务器代码不会暴露给客户端
## 服务器组件 vs 客户端组件
### 服务器组件(默认)
```javascript
// 默认情况下,所有组件都是服务器组件
async function BlogList() {
// 可以直接访问数据库
const posts = await db.post.findMany();
// 可以使用文件系统
const content = await fs.readFile('./content.md', 'utf-8');
return (
<div>
{posts.map(post => (
<PostCard key={post.id} post={post} />
))}
</div>
);
}
```
**特点:**
- 在服务器上渲染
- 不能使用 React Hooks(useState, useEffect 等)
- 不能使用浏览器 API(window, document 等)
- 不能使用事件处理器(onClick, onChange 等)
- 可以直接访问数据库和文件系统
- 不会发送 JavaScript 到客户端
### 客户端组件
```javascript
'use client';
import { useState, useEffect } from 'react';
export default function InteractiveCounter() {
const [count, setCount] = useState(0);
useEffect(() => {
// 可以使用浏览器 API
document.title = `Count: ${count}`;
}, [count]);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
```
**特点:**
- 在客户端渲染
- 可以使用所有 React Hooks
- 可以使用浏览器 API
- 可以使用事件处理器
- 不能直接访问数据库
- 会发送 JavaScript 到客户端
## 混合使用服务器和客户端组件
### 在服务器组件中使用客户端组件
```javascript
// 服务器组件
async function BlogPage() {
const posts = await fetchPosts();
return (
<div>
<h1>Blog Posts</h1>
<PostList posts={posts} />
<LikeButton postId={posts[0].id} />
</div>
);
}
// 客户端组件
'use client';
function LikeButton({ postId }) {
const [liked, setLiked] = useState(false);
return (
<button onClick={() => setLiked(!liked)}>
{liked ? '❤️' : '🤍'}
</button>
);
}
```
### 在客户端组件中使用服务器组件
```javascript
// 客户端组件
'use client';
import dynamic from 'next/dynamic';
// 动态导入服务器组件
const ServerComponent = dynamic(() => import('./ServerComponent'), {
ssr: true
});
export default function ClientComponent() {
return (
<div>
<h1>Client Component</h1>
<ServerComponent />
</div>
);
}
```
## 服务器组件的优势
### 1. 减少客户端 JavaScript
```javascript
// 传统方式(客户端组件)
'use client';
import { useState, useEffect } from 'react';
function BlogList() {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch('/api/posts')
.then(res => res.json())
.then(setPosts);
}, []);
return (
<div>
{posts.map(post => <Post key={post.id} {...post} />)}
</div>
);
}
// 服务器组件方式
async function BlogList() {
const posts = await db.post.findMany();
return (
<div>
{posts.map(post => <Post key={post.id} {...post} />)}
</div>
);
}
```
### 2. 直接访问数据库
```javascript
import { prisma } from '@/lib/prisma';
async function UserDashboard({ userId }) {
// 直接访问数据库,无需 API 路由
const user = await prisma.user.findUnique({
where: { id: userId },
include: {
posts: true,
comments: true
}
});
return (
<div>
<h1>Welcome, {user.name}</h1>
<p>You have {user.posts.length} posts</p>
</div>
);
}
```
### 3. 保持代码私密
```javascript
// 服务器组件中的敏感代码不会暴露给客户端
async function AdminPanel() {
const apiKey = process.env.SECRET_API_KEY;
// 这个 API 调用不会暴露给客户端
const data = await fetch(`https://api.example.com?key=${apiKey}`)
.then(res => res.json());
return <div>{data.content}</div>;
}
```
### 4. 更好的性能
```javascript
// 服务器组件可以并行获取数据
async function Dashboard() {
const [user, posts, notifications] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchNotifications()
]);
return (
<div>
<UserProfile user={user} />
<PostList posts={posts} />
<NotificationList notifications={notifications} />
</div>
);
}
```
## 实际应用场景
### 1. 博客文章列表
```javascript
// app/blog/page.js
async function BlogPage() {
const posts = await db.post.findMany({
orderBy: { createdAt: 'desc' },
take: 10
});
return (
<div>
<h1>Latest Posts</h1>
{posts.map(post => (
<PostCard key={post.id} post={post} />
))}
</div>
);
}
// components/PostCard.js
export default function PostCard({ post }) {
return (
<article>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
<Link href={`/blog/${post.slug}`}>Read more</Link>
</article>
);
}
```
### 2. 电商产品页面
```javascript
// app/products/[id]/page.js
async function ProductPage({ params }) {
const product = await db.product.findUnique({
where: { id: params.id },
include: {
reviews: true,
relatedProducts: true
}
});
return (
<div>
<ProductDetails product={product} />
<ProductReviews reviews={product.reviews} />
<RelatedProducts products={product.relatedProducts} />
<AddToCartButton productId={product.id} />
</div>
);
}
'use client';
function AddToCartButton({ productId }) {
const [loading, setLoading] = useState(false);
const handleAddToCart = async () => {
setLoading(true);
await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify({ productId })
});
setLoading(false);
};
return (
<button onClick={handleAddToCart} disabled={loading}>
{loading ? 'Adding...' : 'Add to Cart'}
</button>
);
}
```
### 3. 仪表板
```javascript
// app/dashboard/page.js
import { auth } from '@/auth';
async function Dashboard() {
const session = await auth();
const [stats, recentActivity, notifications] = await Promise.all([
getUserStats(session.user.id),
getRecentActivity(session.user.id),
getNotifications(session.user.id)
]);
return (
<div>
<DashboardStats stats={stats} />
<RecentActivity activities={recentActivity} />
<NotificationPanel notifications={notifications} />
</div>
);
}
```
## 最佳实践
### 1. 默认使用服务器组件
```javascript
// ✅ 好的做法
async function Page() {
const data = await fetchData();
return <div>{data.content}</div>;
}
// ❌ 不好的做法
'use client';
function Page() {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []);
return <div>{data?.content}</div>;
}
```
### 2. 只在需要交互的地方使用客户端组件
```javascript
// 服务器组件
async function ProductList() {
const products = await fetchProducts();
return (
<div>
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
// 只在需要交互的子组件中使用 'use client'
'use client';
function ProductCard({ product }) {
const [liked, setLiked] = useState(false);
return (
<div>
<h3>{product.name}</h3>
<button onClick={() => setLiked(!liked)}>
{liked ? '❤️' : '🤍'}
</button>
</div>
);
}
```
### 3. 将客户端组件移到组件树的底部
```javascript
// ✅ 好的做法:客户端组件在底部
async function Page() {
const data = await fetchData();
return (
<div>
<Header />
<Content data={data} />
<InteractiveWidget />
</div>
);
}
'use client';
function InteractiveWidget() {
// 交互逻辑
}
// ❌ 不好的做法:客户端组件在顶部
'use client';
function Page() {
const [data, setData] = useState(null);
useEffect(() => {
fetchData().then(setData);
}, []);
return (
<div>
<Header />
<Content data={data} />
</div>
);
}
```
### 4. 使用动态导入减少客户端 JavaScript
```javascript
import dynamic from 'next/dynamic';
// 动态导入重型组件
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <div>Loading...</div>,
ssr: false // 禁用服务器端渲染
});
async function Page() {
const data = await fetchData();
return (
<div>
<LightContent data={data} />
<HeavyComponent />
</div>
);
}
```
## 常见问题
### Q: 如何在服务器组件中使用状态?
A: 服务器组件不能使用 useState,但可以通过以下方式处理:
```javascript
// 使用 URL 参数管理状态
async function Page({ searchParams }) {
const page = parseInt(searchParams.page || '1');
const posts = await getPosts(page);
return (
<div>
<PostList posts={posts} />
<Pagination currentPage={page} />
</div>
);
}
'use client';
function Pagination({ currentPage }) {
const router = useRouter();
return (
<div>
<button onClick={() => router.push(`?page=${currentPage - 1}`)}>
Previous
</button>
<button onClick={() => router.push(`?page=${currentPage + 1}`)}>
Next
</button>
</div>
);
}
```
### Q: 如何在服务器组件中处理表单提交?
A: 使用 Server Actions:
```javascript
'use server';
import { revalidatePath } from 'next/cache';
export async function createPost(formData) {
const title = formData.get('title');
const content = formData.get('content');
await db.post.create({
data: { title, content }
});
revalidatePath('/blog');
}
// 在组件中使用
import { createPost } from './actions';
export default function CreatePostForm() {
return (
<form action={createPost}>
<input name="title" />
<textarea name="content" />
<button type="submit">Create</button>
</form>
);
}
```
React Server Components 是 Next.js 的未来,通过合理使用服务器组件和客户端组件,可以构建出性能更好、用户体验更佳的应用。
服务端 · 2月17日 23:33
Next.js App Router 与 Pages Router 的核心区别及迁移策略是什么?Next.js 13+ 引入了全新的 App Router,与传统的 Pages Router 相比有显著差异。App Router 基于 React Server Components 构建,提供了更强大的功能和更好的性能。
## 主要区别
### 1. 文件结构
**Pages Router:**
```
pages/
index.js
about.js
api/
users.js
```
**App Router:**
```
app/
page.js
about/
page.js
api/
users/
route.js
```
### 2. 布局系统
**App Router 的布局系统更强大:**
```javascript
// app/layout.js
import './globals.css';
export default function RootLayout({ children }) {
return (
<html lang="zh-CN">
<body>
<header>全局头部</header>
{children}
<footer>全局页脚</footer>
</body>
</html>
);
}
// app/about/layout.js
export default function AboutLayout({ children }) {
return (
<div className="about-layout">
<aside>关于我们侧边栏</aside>
<main>{children}</main>
</div>
);
}
```
### 3. 数据获取方式
**Pages Router:**
```javascript
// pages/index.js
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return { props: { data } };
}
export default function Home({ data }) {
return <div>{data.title}</div>;
}
```
**App Router:**
```javascript
// app/page.js
async function getData() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 }
});
return res.json();
}
export default async function Page() {
const data = await getData();
return <div>{data.title}</div>;
}
```
### 4. Server Components vs Client Components
**App Router 默认使用 Server Components:**
```javascript
// app/page.js (Server Component - 默认)
async function Page() {
const data = await fetch('https://api.example.com/data').then(r => r.json());
return <div>{data.title}</div>;
}
export default Page;
// app/components/Interactive.js (Client Component)
'use client';
import { useState } from 'react';
export default function Interactive() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
```
### 5. 路由参数
**Pages Router:**
```javascript
// pages/posts/[id].js
export async function getStaticPaths() {
const posts = await getAllPosts();
return {
paths: posts.map(post => ({ params: { id: post.id } })),
fallback: 'blocking'
};
}
export async function getStaticProps({ params }) {
const post = await getPost(params.id);
return { props: { post } };
}
```
**App Router:**
```javascript
// app/posts/[id]/page.js
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map(post => ({ id: post.id }));
}
export default async function PostPage({ params }) {
const post = await getPost(params.id);
return <article>{post.content}</article>;
}
```
### 6. 加载状态和错误处理
**App Router 内置支持:**
```javascript
// app/posts/loading.js
export default function Loading() {
return <div>加载中...</div>;
}
// app/posts/error.js
'use client';
export default function Error({ error, reset }) {
return (
<div>
<h2>出错了!</h2>
<button onClick={() => reset()}>重试</button>
</div>
);
}
// app/posts/not-found.js
export default function NotFound() {
return <div>文章未找到</div>;
}
```
### 7. Server Actions
**App Router 独有功能:**
```javascript
// app/actions.js
'use server';
import { revalidatePath } from 'next/cache';
export async function createPost(formData) {
const title = formData.get('title');
const content = formData.get('content');
await savePost({ title, content });
revalidatePath('/posts');
}
// app/posts/new/page.js
import { createPost } from '../actions';
export default function NewPostPage() {
return (
<form action={createPost}>
<input name="title" />
<textarea name="content" />
<button type="submit">创建文章</button>
</form>
);
}
```
## 迁移策略
### 渐进式迁移
```javascript
// 可以在同一个项目中同时使用两个 Router
// pages/ - 旧代码继续使用 Pages Router
// app/ - 新功能使用 App Router
// 在 App Router 中链接到 Pages Router
<Link href="/old-page">旧页面</Link>
```
### 关键迁移步骤
1. 安装 Next.js 13+ 并更新配置
2. 创建 `app/` 目录结构
3. 迁移布局到 `app/layout.js`
4. 将页面逐步迁移到 `app/` 目录
5. 更新数据获取逻辑
6. 添加 `'use client'` 指令到交互式组件
7. 更新 API 路由为 Route Handlers
8. 测试并优化性能
## 性能对比
**App Router 优势:**
- 更小的客户端包(Server Components 不发送到客户端)
- 更好的 SEO(默认服务端渲染)
- 更灵活的缓存策略
- 内置加载和错误状态
- 更简洁的代码结构
**Pages Router 优势:**
- 更成熟稳定
- 更丰富的生态系统和文档
- 更简单的学习曲线
- 更多的第三方库支持
## 最佳实践
1. **新项目:** 优先使用 App Router
2. **现有项目:** 渐进式迁移,先迁移简单页面
3. **组件选择:** 默认使用 Server Components,只在需要交互时使用 Client Components
4. **数据获取:** 利用 fetch API 的缓存选项
5. **状态管理:** Server Actions 替代部分 API 路由
App Router 代表了 Next.js 的未来方向,提供了更现代的开发体验和更好的性能表现。
服务端 · 2月17日 23:33
Next.js 中的 SSR、SSG 和 ISR 有什么区别?Next.js 中有三种主要的渲染方式,每种方式都有其特定的使用场景和优势:
## 1. 客户端渲染(CSR)
客户端渲染是传统的 React 应用渲染方式。页面初始加载时返回一个空的 HTML 文件,然后 JavaScript 在浏览器中执行,动态生成页面内容。
**特点:**
- 首屏加载较慢,需要等待 JavaScript 下载和执行
- SEO 不友好,搜索引擎爬虫可能无法抓取动态内容
- 交互性强,适合高度交互的应用
- 适合不需要 SEO 的后台管理系统、仪表盘等
**实现方式:**
```javascript
export default function CSRPage() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('/api/data').then(res => res.json()).then(setData);
}, []);
return <div>{data ? data.content : 'Loading...'}</div>;
}
```
## 2. 服务器端渲染(SSR)
服务器端渲染在每次请求时,服务器会生成完整的 HTML 并发送给客户端。这样可以确保搜索引擎爬虫能够抓取到完整的内容。
**特点:**
- 首屏加载快,HTML 已经在服务器上生成
- SEO 友好,搜索引擎可以抓取完整内容
- 每次请求都需要服务器处理,服务器负载较高
- 适合内容频繁变化、需要 SEO 的页面
**实现方式:**
```javascript
export async function getServerSideProps(context) {
const data = await fetch('https://api.example.com/data').then(res => res.json());
return { props: { data } };
}
export default function SSRPage({ data }) {
return <div>{data.content}</div>;
}
```
## 3. 静态生成(SSG)
静态生成在构建时生成 HTML 文件,这些文件可以被 CDN 缓存,提供最快的加载速度。
**特点:**
- 性能最佳,HTML 文件可以被 CDN 缓存
- SEO 友好,静态内容易于搜索引擎抓取
- 构建时生成,内容更新需要重新构建
- 适合内容不经常变化的页面,如博客、产品页面等
**实现方式:**
```javascript
export async function getStaticProps() {
const data = await fetch('https://api.example.com/data').then(res => res.json());
return {
props: { data },
revalidate: 60 // 可选:启用 ISR,每 60 秒重新生成
};
}
export default function SSGPage({ data }) {
return <div>{data.content}</div>;
}
```
## 4. 增量静态生成(ISR)
ISR 是 SSG 的增强版本,允许在构建后更新静态页面。它结合了 SSG 的性能和 SSR 的动态性。
**特点:**
- 保持静态页面的性能优势
- 可以在后台更新页面内容
- 用户总是看到最新的内容
- 适合需要定期更新但不需要实时更新的内容
**实现方式:**
```javascript
export async function getStaticProps() {
const data = await fetch('https://api.example.com/data').then(res => res.json());
return {
props: { data },
revalidate: 3600 // 每小时重新生成一次
};
}
```
## 选择建议
- **CSR**:后台管理系统、仪表盘、不需要 SEO 的应用
- **SSR**:需要 SEO 且内容频繁变化的页面,如新闻网站、电商产品页
- **SSG**:内容不经常变化的页面,如博客、文档、关于我们页面
- **ISR**:需要定期更新但不需要实时更新的内容,如博客文章、产品列表
在实际项目中,可以根据不同页面的需求混合使用这些渲染方式,以达到最佳的性能和 SEO 效果。
服务端 · 2月17日 23:32
Next.js 的 Pages Router 和 App Router 有什么区别?Next.js 提供了两种主要的路由架构:Pages Router 和 App Router。它们在设计理念、功能和性能方面有显著差异。
## Pages Router
Pages Router 是 Next.js 的传统路由系统,基于 `pages` 目录。
### 特点
1. **文件结构简单**
```
pages/
index.js
about.js
blog/
[slug].js
```
2. **数据获取方法**
- `getStaticProps`:静态生成时获取数据
- `getServerSideProps`:每次请求时获取数据
- `getStaticPaths`:定义动态路由的路径
3. **生命周期**
- 支持 React 的完整生命周期
- 使用 `useEffect` 进行客户端数据获取
4. **路由钩子**
```javascript
import { useRouter } from 'next/router';
const router = useRouter();
console.log(router.pathname, router.query);
```
### 优点
- 成熟稳定,文档和社区支持完善
- 学习曲线较平缓
- 适合中小型项目
### 缺点
- 不支持嵌套路由
- 不支持服务器组件
- 性能优化有限
## App Router
App Router 是 Next.js 13+ 引入的新路由系统,基于 `app` 目录。
### 特点
1. **文件结构**
```
app/
page.js
layout.js
about/
page.js
blog/
[slug]/
page.js
```
2. **服务器组件和客户端组件**
```javascript
// 服务器组件(默认)
async function BlogList() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
return <div>{posts.map(post => <Post key={post.id} {...post} />)}</div>;
}
// 客户端组件
'use client';
import { useState } from 'react';
function InteractiveComponent() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
```
3. **数据获取**
```javascript
// 直接在组件中获取数据
async function Page() {
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 60 } // ISR
}).then(r => r.json());
return <div>{data.content}</div>;
}
```
4. **布局系统**
```javascript
// app/layout.js
export default function RootLayout({ children }) {
return (
<html>
<body>
<Header />
{children}
<Footer />
</body>
</html>
);
}
```
5. **路由钩子**
```javascript
import { useParams, usePathname } from 'next/navigation';
const params = useParams();
const pathname = usePathname();
```
### 优点
- 支持服务器组件,减少客户端 JavaScript
- 支持嵌套路由和布局
- 更好的性能和用户体验
- 更现代的 API 设计
### 缺点
- 学习曲线较陡峭
- 相对较新,生态系统还在发展
- 需要理解服务器组件和客户端组件的区别
## 主要区别对比
| 特性 | Pages Router | App Router |
|------|--------------|------------|
| 目录 | `pages/` | `app/` |
| 服务器组件 | 不支持 | 支持 |
| 嵌套路由 | 不支持 | 支持 |
| 布局系统 | 有限 | 强大 |
| 数据获取 | getStaticProps/getServerSideProps | 直接在组件中 |
| 路由钩子 | next/router | next/navigation |
| 文件约定 | index.js | page.js |
| 加载状态 | 需要手动实现 | 自动支持 loading.js |
| 错误处理 | 需要手动实现 | 自动支持 error.js |
| 流式渲染 | 不支持 | 支持 |
## 迁移建议
### 何时使用 Pages Router
- 现有项目已经在使用 Pages Router
- 项目规模较小,不需要复杂的功能
- 团队对 Pages Router 更熟悉
### 何时使用 App Router
- 新项目
- 需要服务器组件来优化性能
- 需要嵌套路由和复杂布局
- 需要更好的 SEO 和性能
### 混合使用
Next.js 允许同时使用两种路由器:
- `pages/` 目录使用 Pages Router
- `app/` 目录使用 App Router
```javascript
// 两个目录可以共存
pages/
api/
hello.js // API 路由
app/
page.js // 主页面
```
## 最佳实践
1. **新项目优先使用 App Router**:获得更好的性能和开发体验
2. **逐步迁移**:现有项目可以逐步将页面迁移到 App Router
3. **合理使用服务器组件**:将不需要交互的组件设为服务器组件
4. **利用布局系统**:使用嵌套布局来共享 UI
5. **保持一致性**:在一个项目中尽量统一使用一种路由器
选择哪种路由器取决于项目需求、团队经验和性能要求。App Router 代表了 Next.js 的未来方向,但 Pages Router 仍然是可靠的选择。
服务端 · 2月17日 23:32
Next.js 有哪些性能优化技术?Next.js 提供了多种性能优化技术,帮助开发者构建高性能的 Web 应用。以下是 Next.js 的主要性能优化策略:
## 1. 自动代码分割
Next.js 自动将代码分割成小块,只加载当前页面所需的代码。
```javascript
// pages/index.js
import dynamic from 'next/dynamic';
// 动态导入组件
const DynamicComponent = dynamic(() => import('../components/HeavyComponent'), {
loading: () => <p>Loading...</p>,
ssr: false // 禁用服务器端渲染
});
export default function Home() {
return (
<div>
<h1>Home Page</h1>
<DynamicComponent />
</div>
);
}
```
## 2. 图片优化
使用 `next/image` 组件自动优化图片。
```javascript
import Image from 'next/image';
export default function ImageExample() {
return (
<Image
src="/hero.jpg"
alt="Hero image"
width={800}
height={600}
priority // 首屏图片使用优先加载
placeholder="blur" // 模糊占位符
blurDataURL="data:image/jpeg;base64,..."
/>
);
}
```
**图片优化特性:**
- 自动选择最佳格式(WebP、AVIF)
- 响应式图片
- 懒加载
- 避免布局偏移
## 3. 字体优化
使用 `next/font` 优化字体加载。
```javascript
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
});
export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.variable}>
<body>{children}</body>
</html>
);
}
```
## 4. 数据获取优化
### 使用缓存和 ISR
```javascript
// 使用 fetch 的缓存选项
async function Page() {
const data = await fetch('https://api.example.com/data', {
next: {
revalidate: 60, // ISR:每 60 秒重新验证
tags: ['data'] // 标签用于按需重新验证
}
}).then(r => r.json());
return <div>{data.content}</div>;
}
```
### 使用 React Query 或 SWR
```javascript
'use client';
import useSWR from 'swr';
const fetcher = (url) => fetch(url).then(r => r.json());
export default function DataComponent() {
const { data, error, isLoading } = useSWR('/api/data', fetcher, {
revalidateOnFocus: false,
dedupingInterval: 60000,
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error</div>;
return <div>{data.content}</div>;
}
```
## 5. 预加载和预取
```javascript
import Link from 'next/link';
export default function Navigation() {
return (
<nav>
<Link href="/about" prefetch={true}>
About
</Link>
<Link href="/contact" prefetch={false}>
Contact
</Link>
</nav>
);
}
```
## 6. 脚本优化
使用 `next/script` 优化第三方脚本加载。
```javascript
import Script from 'next/script';
export default function Page() {
return (
<>
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive"
/>
<Script id="google-analytics" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
`}
</Script>
</>
);
}
```
**脚本加载策略:**
- `beforeInteractive`:在页面交互前加载
- `afterInteractive`:在页面可交互后立即加载
- `lazyOnload`:在浏览器空闲时加载
## 7. 使用 React.memo 和 useMemo
```javascript
'use client';
import { memo, useMemo } from 'react';
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
const processedData = useMemo(() => {
return data.map(item => ({
...item,
computed: expensiveCalculation(item)
}));
}, [data]);
return <div>{/* 渲染处理后的数据 */}</div>;
});
```
## 8. 虚拟化长列表
```javascript
'use client';
import { useVirtualizer } from '@tanstack/react-virtual';
export default function VirtualList({ items }) {
const parentRef = useRef();
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
{virtualizer.getVirtualItems().map(virtualItem => (
<div
key={virtualItem.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualItem.size}px`,
transform: `translateY(${virtualItem.start}px)`,
}}
>
{items[virtualItem.index]}
</div>
))}
</div>
</div>
);
}
```
## 9. 服务器组件优化
```javascript
// 服务器组件默认不发送 JavaScript 到客户端
async function ServerComponent() {
const data = await fetchData();
return (
<div>
<h1>{data.title}</h1>
<p>{data.content}</p>
</div>
);
}
// 只在需要交互的地方使用客户端组件
'use client';
function InteractiveComponent() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
```
## 10. 使用 Streaming
```javascript
import { Suspense } from 'react';
async function SlowComponent() {
const data = await slowFetch();
return <div>{data}</div>;
}
export default function Page() {
return (
<div>
<h1>Page Title</h1>
<Suspense fallback={<div>Loading...</div>}>
<SlowComponent />
</Suspense>
</div>
);
}
```
## 11. 缓存策略
### 使用 Next.js 缓存
```javascript
// 缓存 API 响应
export async function getStaticProps() {
const data = await fetch('https://api.example.com/data', {
cache: 'force-cache', // 或 'no-store', 'no-cache'
}).then(r => r.json());
return {
props: { data },
revalidate: 3600, // 1 小时
};
}
```
### 使用 Redis 缓存
```javascript
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN,
});
export async function getCachedData(key) {
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const data = await fetchData();
await redis.set(key, JSON.stringify(data), { ex: 3600 });
return data;
}
```
## 12. 构建优化
### 分析构建输出
```javascript
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({
// 其他配置
});
```
### 压缩和优化
```javascript
// next.config.js
module.exports = {
compress: true,
swcMinify: true,
productionBrowserSourceMaps: false,
// 优化图片
images: {
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
},
};
```
## 性能监控
### 使用 Web Vitals
```javascript
// pages/_app.js
import { useReportWebVitals } from 'next/web-vitals';
export function reportWebVitals(metric) {
// 发送到分析服务
console.log(metric);
// 或发送到 Google Analytics
// gtag('event', metric.name, { value: metric.value });
}
export default function App({ Component, pageProps }) {
useReportWebVitals(reportWebVitals);
return <Component {...pageProps} />;
}
```
## 最佳实践
1. **使用服务器组件**:减少客户端 JavaScript
2. **优化图片**:使用 next/image 组件
3. **懒加载**:延迟加载非关键资源
4. **缓存数据**:使用 ISR 和缓存策略
5. **监控性能**:使用 Web Vitals 监控
6. **分析构建**:定期分析 bundle 大小
7. **使用 CDN**:部署到 Vercel 或其他 CDN
8. **优化字体**:使用 next/font 优化字体加载
通过合理使用这些优化技术,可以显著提升 Next.js 应用的性能和用户体验。
服务端 · 2月17日 23:32
Next.js 中有哪些数据获取方法?Next.js 提供了多种数据获取方法,开发者可以根据不同的渲染策略和需求选择合适的方式。以下是 Next.js 中主要的数据获取方法:
## Pages Router 数据获取方法
### 1. getStaticProps
在构建时获取数据,用于静态生成(SSG)。
```javascript
export async function getStaticProps(context) {
const data = await fetch('https://api.example.com/data').then(r => r.json());
return {
props: { data },
revalidate: 60, // 可选:ISR,每 60 秒重新生成
notFound: false, // 可选:返回 404 页面
redirect: { destination: '/login', permanent: false }, // 可选:重定向
};
}
export default function Page({ data }) {
return <div>{data.content}</div>;
}
```
**适用场景:**
- 数据在构建时可用
- 页面内容不经常变化
- 需要预渲染以提升 SEO
### 2. getServerSideProps
在每次请求时获取数据,用于服务器端渲染(SSR)。
```javascript
export async function getServerSideProps(context) {
const { req, res, query, params } = context;
// 可以访问请求和响应对象
const token = req.cookies.token;
const data = await fetch('https://api.example.com/data', {
headers: { Authorization: `Bearer ${token}` }
}).then(r => r.json());
return {
props: { data },
// 不支持 revalidate
};
}
export default function Page({ data }) {
return <div>{data.content}</div>;
}
```
**适用场景:**
- 数据在请求时才能获取
- 需要访问请求/响应对象
- 内容频繁变化
### 3. getStaticPaths
用于动态路由的静态生成,定义所有可能的路径。
```javascript
export async function getStaticPaths() {
const posts = await getAllPosts();
return {
paths: posts.map(post => ({
params: { slug: post.slug }
})),
fallback: false, // 或 'blocking' 或 true
};
}
export async function getStaticProps({ params }) {
const post = await getPostBySlug(params.slug);
return {
props: { post },
};
}
export default function BlogPost({ post }) {
return <div>{post.title}</div>;
}
```
**fallback 选项:**
- `false`:只返回预生成的路径,其他路径返回 404
- `'blocking'`:服务器渲染新路径,等待完成后返回
- `true`:立即返回静态页面,后台生成新路径
## App Router 数据获取方法
### 1. 服务器组件中的 fetch
在服务器组件中直接使用 fetch 获取数据。
```javascript
async function Page() {
const data = await fetch('https://api.example.com/data', {
cache: 'force-cache', // 或 'no-store', 'no-cache', 'default'
next: {
revalidate: 60, // ISR
tags: ['data'] // 用于按需重新验证
}
}).then(r => r.json());
return <div>{data.content}</div>;
}
```
**cache 选项:**
- `force-cache`:强制使用缓存(默认)
- `no-store`:不使用缓存
- `no-cache`:每次验证缓存
- `default`:使用默认缓存策略
### 2. 使用 React Server Components
```javascript
async function BlogList() {
const posts = await fetch('https://api.example.com/posts', {
next: { revalidate: 3600 }
}).then(r => r.json());
return (
<div>
{posts.map(post => (
<PostCard key={post.id} post={post} />
))}
</div>
);
}
```
### 3. 使用 Suspense 和 Streaming
```javascript
import { Suspense } from 'react';
async function SlowComponent() {
const data = await fetch('https://api.example.com/slow', {
next: { revalidate: 60 }
}).then(r => r.json());
return <div>{data.content}</div>;
}
export default function Page() {
return (
<div>
<h1>Page Title</h1>
<Suspense fallback={<div>Loading...</div>}>
<SlowComponent />
</Suspense>
</div>
);
}
```
## 客户端数据获取
### 1. 使用 useEffect
```javascript
'use client';
import { useState, useEffect } from 'react';
export default function ClientDataComponent() {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
});
}, []);
if (loading) return <div>Loading...</div>;
return <div>{data.content}</div>;
}
```
### 2. 使用 SWR
```javascript
'use client';
import useSWR from 'swr';
const fetcher = (url) => fetch(url).then(res => res.json());
export default function SWRComponent() {
const { data, error, isLoading } = useSWR('/api/data', fetcher, {
revalidateOnFocus: false,
revalidateOnReconnect: false,
dedupingInterval: 60000,
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error</div>;
return <div>{data.content}</div>;
}
```
### 3. 使用 React Query
```javascript
'use client';
import { useQuery } from '@tanstack/react-query';
async function fetchData() {
const res = await fetch('/api/data');
return res.json();
}
export default function ReactQueryComponent() {
const { data, error, isLoading } = useQuery({
queryKey: ['data'],
queryFn: fetchData,
staleTime: 60000,
cacheTime: 300000,
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error</div>;
return <div>{data.content}</div>;
}
```
## 数据获取最佳实践
### 1. 选择合适的方法
| 场景 | 推荐方法 |
|------|----------|
| 静态内容,构建时可用 | getStaticProps / SSG |
| 动态内容,需要实时数据 | getServerSideProps / SSR |
| 需要用户交互 | 客户端数据获取 |
| SEO 重要,内容变化不频繁 | SSG + ISR |
| 需要访问请求/响应对象 | getServerSideProps |
### 2. 缓存策略
```javascript
// 长期缓存
fetch('/api/data', { cache: 'force-cache', next: { revalidate: 3600 } });
// 短期缓存
fetch('/api/data', { cache: 'no-store' });
// 按需重新验证
fetch('/api/data', { next: { tags: ['data'] } });
// 在 API 路由中重新验证
import { revalidateTag } from 'next/cache';
export async function POST() {
revalidateTag('data');
return Response.json({ revalidated: true });
}
```
### 3. 错误处理
```javascript
export async function getStaticProps() {
try {
const data = await fetchData();
return { props: { data } };
} catch (error) {
return {
notFound: true,
};
}
}
```
### 4. 加载状态
```javascript
// App Router - 使用 loading.js
// app/loading.js
export default function Loading() {
return <div>Loading...</div>;
}
// Pages Router - 使用自定义加载组件
export default function LoadingPage() {
return <div>Loading...</div>;
}
```
### 5. 并行数据获取
```javascript
// 并行获取多个数据
export async function getStaticProps() {
const [posts, users, comments] = await Promise.all([
fetch('/api/posts').then(r => r.json()),
fetch('/api/users').then(r => r.json()),
fetch('/api/comments').then(r => r.json()),
]);
return {
props: { posts, users, comments },
};
}
```
## 性能优化建议
1. **使用 ISR**:对于需要定期更新的内容,使用 ISR 而不是 SSR
2. **缓存数据**:合理设置缓存时间,减少不必要的请求
3. **并行获取**:使用 Promise.all 并行获取多个数据源
4. **流式渲染**:使用 Suspense 实现流式渲染,提升用户体验
5. **客户端缓存**:使用 SWR 或 React Query 缓存客户端数据
6. **按需重新验证**:使用标签系统按需重新验证数据
通过合理选择和使用这些数据获取方法,可以构建高性能、用户体验良好的 Next.js 应用。
服务端 · 2月17日 23:32
Next.js 中如何进行状态管理?Next.js 提供了多种状态管理解决方案,开发者可以根据项目需求选择合适的方式。以下是 Next.js 中常用的状态管理方法:
## 1. React 内置状态管理
### useState Hook
用于管理组件的本地状态。
```javascript
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
```
### useReducer Hook
用于管理复杂的状态逻辑。
```javascript
'use client';
import { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
```
### useContext Hook
用于跨组件共享状态。
```javascript
'use client';
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// 使用
export default function App() {
return (
<ThemeProvider>
<Header />
<Content />
</ThemeProvider>
);
}
function Header() {
const { theme, setTheme } = useTheme();
return (
<header>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Toggle Theme
</button>
</header>
);
}
```
## 2. 全局状态管理库
### Zustand
轻量级、简单的状态管理库。
```javascript
// store/useStore.js
import { create } from 'zustand';
export const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
// 使用
'use client';
import { useStore } from '@/store/useStore';
export default function Counter() {
const { count, increment, decrement, reset } = useStore();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
}
```
### Redux Toolkit
功能强大的状态管理库,适合大型应用。
```javascript
// store/slices/counterSlice.js
import { createSlice } from '@reduxjs/toolkit';
export const counterSlice = createSlice({
name: 'counter',
initialState: {
value: 0,
},
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
incrementByAmount: (state, action) => {
state.value += action.payload;
},
},
});
export const { increment, decrement, incrementByAmount } = counterSlice.actions;
export default counterSlice.reducer;
// store/index.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './slices/counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
// store/hooks.js
import { useDispatch, useSelector } from 'react-redux';
import type { TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from './index';
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// 使用
'use client';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { increment, decrement, incrementByAmount } from '@/store/slices/counterSlice';
export default function Counter() {
const count = useAppSelector((state) => state.counter.value);
const dispatch = useAppDispatch();
return (
<div>
<p>Count: {count}</p>
<button onClick={() => dispatch(increment())}>+</button>
<button onClick={() => dispatch(decrement())}>-</button>
<button onClick={() => dispatch(incrementByAmount(10))}>+10</button>
</div>
);
}
```
### Jotai
原子化状态管理,类似于 Recoil。
```javascript
// store/atoms.js
import { atom } from 'jotai';
export const countAtom = atom(0);
export const doubledCountAtom = atom((get) => get(countAtom) * 2);
// 使用
'use client';
import { useAtom } from 'jotai';
import { countAtom, doubledCountAtom } from '@/store/atoms';
export default function Counter() {
const [count, setCount] = useAtom(countAtom);
const [doubledCount] = useAtom(doubledCountAtom);
return (
<div>
<p>Count: {count}</p>
<p>Doubled: {doubledCount}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
```
## 3. 服务器状态管理
### SWR
用于数据获取和缓存。
```javascript
'use client';
import useSWR from 'swr';
const fetcher = (url) => fetch(url).then((res) => res.json());
export default function UserProfile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error</div>;
return (
<div>
<h1>{data.name}</h1>
<p>{data.email}</p>
</div>
);
}
```
### React Query (TanStack Query)
强大的数据获取和状态管理库。
```javascript
'use client';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
async function fetchUser() {
const res = await fetch('/api/user');
return res.json();
}
async function updateUser(data) {
const res = await fetch('/api/user', {
method: 'PUT',
body: JSON.stringify(data),
});
return res.json();
}
export default function UserProfile() {
const queryClient = useQueryClient();
const { data, isLoading, error } = useQuery({
queryKey: ['user'],
queryFn: fetchUser,
});
const mutation = useMutation({
mutationFn: updateUser,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['user'] });
},
});
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error</div>;
return (
<div>
<h1>{data.name}</h1>
<p>{data.email}</p>
<button onClick={() => mutation.mutate({ name: 'New Name' })}>
Update Name
</button>
</div>
);
}
```
## 4. 表单状态管理
### React Hook Form
高性能的表单状态管理。
```javascript
'use client';
import { useForm } from 'react-hook-form';
export default function ContactForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>Name</label>
<input {...register('name', { required: true })} />
{errors.name && <span>This field is required</span>}
</div>
<div>
<label>Email</label>
<input
{...register('email', {
required: true,
pattern: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i
})}
/>
{errors.email && <span>Invalid email</span>}
</div>
<button type="submit">Submit</button>
</form>
);
}
```
## 5. URL 状态管理
### 使用 URL 查询参数
```javascript
'use client';
import { useRouter, useSearchParams } from 'next/navigation';
export default function ProductList() {
const router = useRouter();
const searchParams = useSearchParams();
const page = parseInt(searchParams.get('page') || '1');
const category = searchParams.get('category') || 'all';
const handlePageChange = (newPage) => {
const params = new URLSearchParams(searchParams.toString());
params.set('page', newPage.toString());
router.push(`?${params.toString()}`);
};
const handleCategoryChange = (newCategory) => {
const params = new URLSearchParams(searchParams.toString());
params.set('category', newCategory);
params.set('page', '1');
router.push(`?${params.toString()}`);
};
return (
<div>
<select
value={category}
onChange={(e) => handleCategoryChange(e.target.value)}
>
<option value="all">All</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
</select>
<div>Current page: {page}</div>
<button onClick={() => handlePageChange(page - 1)}>Previous</button>
<button onClick={() => handlePageChange(page + 1)}>Next</button>
</div>
);
}
```
## 6. 服务器组件状态
### 使用服务器组件
```javascript
// 服务器组件不需要客户端状态管理
async function ProductList() {
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 }
}).then(r => r.json());
return (
<div>
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
}
```
## 状态管理最佳实践
1. **选择合适的工具**:
- 简单状态:useState, useReducer
- 跨组件状态:useContext
- 全局状态:Zustand, Redux Toolkit
- 服务器状态:SWR, React Query
- 表单状态:React Hook Form
2. **最小化状态**:只存储必要的状态,其他状态通过计算得出
3. **服务器优先**:尽可能使用服务器组件,减少客户端状态
4. **避免过度设计**:不要为简单的状态引入复杂的状态管理库
5. **类型安全**:使用 TypeScript 确保类型安全
6. **性能优化**:使用 React.memo, useMemo, useCallback 优化性能
7. **持久化**:使用 localStorage 或 IndexedDB 持久化重要状态
通过合理选择和使用这些状态管理方法,可以构建高效、可维护的 Next.js 应用。
服务端 · 2月17日 23:32
Next.js 中如何实现身份验证?Next.js 提供了多种身份验证解决方案,开发者可以根据项目需求选择合适的方式。以下是 Next.js 中常用的身份验证方法:
## 1. NextAuth.js
NextAuth.js 是 Next.js 最流行的身份验证库,提供完整的身份验证解决方案。
### 基本配置
```javascript
// app/api/auth/[...nextauth]/route.js
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import GoogleProvider from 'next-auth/providers/google';
export const { handlers, auth, signIn, signOut } = NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
const user = await authenticateUser(credentials);
if (user) {
return user;
}
return null;
}
}),
],
pages: {
signIn: '/auth/signin',
error: '/auth/error',
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.id = user.id;
token.role = user.role;
}
return token;
},
async session({ session, token }) {
session.user.id = token.id;
session.user.role = token.role;
return session;
},
},
session: {
strategy: 'jwt',
},
secret: process.env.NEXTAUTH_SECRET,
});
export { handlers as GET, handlers as POST };
```
### 使用 Session
```javascript
'use client';
import { useSession, signIn, signOut } from 'next-auth/react';
export default function Navbar() {
const { data: session, status } = useSession();
if (status === 'loading') {
return <div>Loading...</div>;
}
return (
<nav>
{session ? (
<>
<p>Welcome, {session.user.name}</p>
<button onClick={() => signOut()}>Sign Out</button>
</>
) : (
<button onClick={() => signIn()}>Sign In</button>
)}
</nav>
);
}
```
### 服务器端获取 Session
```javascript
import { auth } from '@/auth';
export default async function Dashboard() {
const session = await auth();
if (!session) {
redirect('/auth/signin');
}
return (
<div>
<h1>Welcome, {session.user.name}</h1>
<p>Role: {session.user.role}</p>
</div>
);
}
```
### 保护路由
```javascript
import { auth } from '@/auth';
export default async function ProtectedPage() {
const session = await auth();
if (!session) {
redirect('/auth/signin');
}
return <div>Protected content</div>;
}
```
## 2. 自定义 JWT 实现
使用 Next.js API Routes 和 JWT 实现自定义身份验证。
### JWT 工具函数
```javascript
// lib/auth.js
import jwt from 'jsonwebtoken';
const SECRET = process.env.JWT_SECRET;
export function signToken(payload) {
return jwt.sign(payload, SECRET, { expiresIn: '7d' });
}
export function verifyToken(token) {
try {
return jwt.verify(token, SECRET);
} catch (error) {
return null;
}
}
export function setAuthCookie(res, token) {
res.setHeader('Set-Cookie', `token=${token}; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=604800`);
}
export function clearAuthCookie(res) {
res.setHeader('Set-Cookie', 'token=; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=0');
}
```
### 登录 API
```javascript
// app/api/auth/login/route.js
import { signToken, setAuthCookie } from '@/lib/auth';
import { compare } from 'bcryptjs';
import { db } from '@/lib/db';
export async function POST(request) {
try {
const { email, password } = await request.json();
const user = await db.user.findUnique({ where: { email } });
if (!user || !await compare(password, user.password)) {
return Response.json(
{ error: 'Invalid credentials' },
{ status: 401 }
);
}
const token = signToken({ userId: user.id, email: user.email });
const response = Response.json({ user: { id: user.id, email: user.email, name: user.name } });
setAuthCookie(response, token);
return response;
} catch (error) {
return Response.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
```
### 注册 API
```javascript
// app/api/auth/register/route.js
import { hash } from 'bcryptjs';
import { db } from '@/lib/db';
export async function POST(request) {
try {
const { email, password, name } = await request.json();
const existingUser = await db.user.findUnique({ where: { email } });
if (existingUser) {
return Response.json(
{ error: 'User already exists' },
{ status: 400 }
);
}
const hashedPassword = await hash(password, 10);
const user = await db.user.create({
data: {
email,
password: hashedPassword,
name,
},
});
return Response.json(
{ user: { id: user.id, email: user.email, name: user.name } },
{ status: 201 }
);
} catch (error) {
return Response.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
```
### 获取当前用户
```javascript
// lib/getCurrentUser.js
import { cookies } from 'next/headers';
import { verifyToken } from '@/lib/auth';
import { db } from '@/lib/db';
export async function getCurrentUser() {
const cookieStore = await cookies();
const token = cookieStore.get('token')?.value;
if (!token) {
return null;
}
const decoded = verifyToken(token);
if (!decoded) {
return null;
}
const user = await db.user.findUnique({
where: { id: decoded.userId },
select: { id: true, email: true, name: true, role: true }
});
return user;
}
```
## 3. 中间件保护路由
```javascript
// middleware.js
import { NextResponse } from 'next/server';
import { verifyToken } from '@/lib/auth';
export function middleware(request) {
const token = request.cookies.get('token')?.value;
const { pathname } = request.nextUrl;
// 公开路由
const publicPaths = ['/auth/signin', '/auth/register', '/api/auth/login', '/api/auth/register'];
if (publicPaths.some(path => pathname.startsWith(path))) {
return NextResponse.next();
}
// API 路由
if (pathname.startsWith('/api')) {
if (!token || !verifyToken(token)) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
return NextResponse.next();
}
// 页面路由
if (!token || !verifyToken(token)) {
return NextResponse.redirect(new URL('/auth/signin', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico).*)',
],
};
```
## 4. OAuth 集成
### Google OAuth
```javascript
// app/api/auth/google/route.js
import { OAuth2Client } from 'google-auth-library';
import { signToken, setAuthCookie } from '@/lib/auth';
import { db } from '@/lib/db';
const client = new OAuth2Client(process.env.GOOGLE_CLIENT_ID);
export async function POST(request) {
try {
const { idToken } = await request.json();
const ticket = await client.verifyIdToken({
idToken,
audience: process.env.GOOGLE_CLIENT_ID,
});
const payload = ticket.getPayload();
let user = await db.user.findUnique({ where: { email: payload.email } });
if (!user) {
user = await db.user.create({
data: {
email: payload.email,
name: payload.name,
image: payload.picture,
provider: 'google',
},
});
}
const token = signToken({ userId: user.id, email: user.email });
const response = Response.json({ user: { id: user.id, email: user.email, name: user.name } });
setAuthCookie(response, token);
return response;
} catch (error) {
return Response.json(
{ error: 'Authentication failed' },
{ status: 401 }
);
}
}
```
## 5. 权限控制
### 基于角色的访问控制(RBAC)
```javascript
// lib/permissions.js
export const ROLES = {
ADMIN: 'admin',
USER: 'user',
GUEST: 'guest',
};
export function hasPermission(userRole, requiredRole) {
const roleHierarchy = {
[ROLES.ADMIN]: 3,
[ROLES.USER]: 2,
[ROLES.GUEST]: 1,
};
return roleHierarchy[userRole] >= roleHierarchy[requiredRole];
}
// 使用示例
export default function AdminPanel({ user }) {
if (!hasPermission(user.role, ROLES.ADMIN)) {
return <div>Access denied</div>;
}
return <div>Admin content</div>;
}
```
### 服务器端权限检查
```javascript
import { getCurrentUser } from '@/lib/getCurrentUser';
import { ROLES, hasPermission } from '@/lib/permissions';
export default async function AdminPage() {
const user = await getCurrentUser();
if (!user || !hasPermission(user.role, ROLES.ADMIN)) {
redirect('/');
}
return <div>Admin content</div>;
}
```
## 6. 密码重置
### 发送重置邮件
```javascript
// app/api/auth/forgot-password/route.js
import { db } from '@/lib/db';
import { signToken } from '@/lib/auth';
import { sendEmail } from '@/lib/email';
export async function POST(request) {
try {
const { email } = await request.json();
const user = await db.user.findUnique({ where: { email } });
if (!user) {
// 不透露用户是否存在
return Response.json({ message: 'If user exists, email sent' });
}
const resetToken = signToken(
{ userId: user.id, type: 'password_reset' },
{ expiresIn: '1h' }
);
await sendEmail({
to: email,
subject: 'Password Reset',
html: `<a href="${process.env.APP_URL}/auth/reset-password?token=${resetToken}">Reset Password</a>`,
});
return Response.json({ message: 'Email sent' });
} catch (error) {
return Response.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
```
### 重置密码
```javascript
// app/api/auth/reset-password/route.js
import { verifyToken } from '@/lib/auth';
import { hash } from 'bcryptjs';
import { db } from '@/lib/db';
export async function POST(request) {
try {
const { token, password } = await request.json();
const decoded = verifyToken(token);
if (!decoded || decoded.type !== 'password_reset') {
return Response.json(
{ error: 'Invalid or expired token' },
{ status: 400 }
);
}
const hashedPassword = await hash(password, 10);
await db.user.update({
where: { id: decoded.userId },
data: { password: hashedPassword },
});
return Response.json({ message: 'Password reset successful' });
} catch (error) {
return Response.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
```
## 身份验证最佳实践
1. **使用 HTTPS**:始终使用 HTTPS 传输敏感数据
2. **HttpOnly Cookies**:使用 HttpOnly 防止 XSS 攻击
3. **CSRF 保护**:实施 CSRF 令牌保护
4. **密码哈希**:使用 bcrypt 或 argon2 哈希密码
5. **速率限制**:防止暴力破解攻击
6. **安全头部**:设置适当的安全头部
7. **日志记录**:记录身份验证事件用于审计
8. **会话管理**:设置合理的会话过期时间
9. **多因素认证**:为敏感操作实施 MFA
10. **定期更新**:保持依赖项和安全补丁更新
通过合理实施这些身份验证方法,可以构建安全可靠的 Next.js 应用。
服务端 · 2月17日 23:32
Next.js 如何部署到生产环境?Next.js 提供了多种部署选项,开发者可以根据项目需求选择最适合的部署方式。以下是 Next.js 的主要部署选项和最佳实践:
## 1. Vercel(推荐)
Vercel 是 Next.js 的创建者提供的托管平台,提供最佳的 Next.js 部署体验。
### 优点
- 零配置部署
- 自动 HTTPS
- 全球 CDN
- 边缘函数支持
- 预览部署
- 自动优化
### 部署步骤
```bash
# 1. 安装 Vercel CLI
npm i -g vercel
# 2. 登录 Vercel
vercel login
# 3. 部署
vercel
# 4. 生产环境部署
vercel --prod
```
### 配置文件
```javascript
// vercel.json
{
"buildCommand": "npm run build",
"outputDirectory": ".next",
"framework": "nextjs",
"regions": ["iad1"],
"functions": {
"app/api/**/*.js": {
"maxDuration": 30
}
},
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "DENY"
}
]
}
]
}
```
## 2. 自托管(Docker)
使用 Docker 容器化 Next.js 应用,部署到任何支持 Docker 的平台。
### Dockerfile
```dockerfile
# 多阶段构建
FROM node:18-alpine AS base
# 依赖阶段
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# 构建阶段
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# 运行阶段
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
ENV HOSTNAME "0.0.0.0"
CMD ["node", "server.js"]
```
### docker-compose.yml
```yaml
version: '3.8'
services:
nextjs:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
restart: unless-stopped
```
### 构建和运行
```bash
# 构建镜像
docker build -t nextjs-app .
# 运行容器
docker run -p 3000:3000 nextjs-app
# 使用 docker-compose
docker-compose up -d
```
## 3. Node.js 服务器
将 Next.js 应用部署到传统的 Node.js 服务器。
### 使用 PM2
```javascript
// ecosystem.config.js
module.exports = {
apps: [{
name: 'nextjs-app',
script: 'node_modules/next/dist/bin/next',
args: 'start -p 3000',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z'
}]
};
```
```bash
# 安装 PM2
npm install -g pm2
# 启动应用
pm2 start ecosystem.config.js
# 查看状态
pm2 status
# 查看日志
pm2 logs
# 重启应用
pm2 restart nextjs-app
```
### 使用 Nginx 反向代理
```nginx
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
```
## 4. 静态导出
对于不需要服务器端功能的简单应用,可以导出为静态 HTML。
### 配置
```javascript
// next.config.js
module.exports = {
output: 'export',
images: {
unoptimized: true
}
};
```
### 构建和部署
```bash
# 构建
npm run build
# 输出在 out/ 目录
# 可以部署到任何静态托管服务,如:
# - GitHub Pages
# - Netlify
# - AWS S3 + CloudFront
# - Firebase Hosting
```
## 5. 云平台部署
### AWS
#### 使用 AWS Amplify
```bash
# 安装 Amplify CLI
npm install -g @aws-amplify/cli
# 初始化
amplify init
# 添加托管
amplify add hosting
# 发布
amplify publish
```
#### 使用 AWS Lambda
```javascript
// app/api/hello/route.js
export const runtime = 'edge';
export async function GET() {
return Response.json({ message: 'Hello from Edge!' });
}
```
### Google Cloud
#### 使用 Cloud Run
```bash
# 构建镜像
gcloud builds submit --tag gcr.io/PROJECT_ID/nextjs-app
# 部署到 Cloud Run
gcloud run deploy nextjs-app \
--image gcr.io/PROJECT_ID/nextjs-app \
--platform managed \
--region us-central1 \
--allow-unauthenticated
```
### Azure
#### 使用 Azure Static Web Apps
```bash
# 安装 Azure CLI
npm install -g @azure/static-web-apps-cli
# 部署
swa deploy ./out --env production
```
## 6. CI/CD 集成
### GitHub Actions
```yaml
name: Deploy to Vercel
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Build
run: npm run build
- name: Deploy to Vercel
uses: amondnet/vercel-action@v20
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.ORG_ID }}
vercel-project-id: ${{ secrets.PROJECT_ID }}
vercel-args: '--prod'
```
### GitLab CI
```yaml
image: node:18
stages:
- test
- build
- deploy
test:
stage: test
script:
- npm ci
- npm test
build:
stage: build
script:
- npm ci
- npm run build
artifacts:
paths:
- .next/
deploy:
stage: deploy
script:
- npm install -g vercel
- vercel --prod --token=$VERCEL_TOKEN
only:
- main
```
## 环境变量管理
### .env 文件
```bash
# .env.local(本地开发)
DATABASE_URL=postgresql://localhost/mydb
API_KEY=your_api_key
# .env.production(生产环境)
DATABASE_URL=postgresql://prod-db-host/mydb
API_KEY=prod_api_key
```
### Vercel 环境变量
```bash
# 使用 Vercel CLI
vercel env add DATABASE_URL production
# 或在 Vercel Dashboard 中设置
```
## 性能优化
### 1. 启用压缩
```javascript
// next.config.js
module.exports = {
compress: true,
};
```
### 2. 优化图片
```javascript
// next.config.js
module.exports = {
images: {
formats: ['image/avif', 'image/webp'],
deviceSizes: [640, 750, 828, 1080, 1200, 1920],
},
};
```
### 3. 启用 SWC 压缩
```javascript
// next.config.js
module.exports = {
swcMinify: true,
};
```
## 监控和日志
### 使用 Vercel Analytics
```javascript
// pages/_app.js
import { Analytics } from '@vercel/analytics/react';
export default function App({ Component, pageProps }) {
return (
<>
<Component {...pageProps} />
<Analytics />
</>
);
}
```
### 使用 Sentry
```javascript
// sentry.client.config.js
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 1.0,
});
```
## 部署最佳实践
1. **使用 Vercel**:获得最佳的 Next.js 部署体验
2. **环境变量**:使用 .env 文件管理环境变量
3. **CI/CD**:设置自动部署流程
4. **监控**:使用监控工具跟踪应用性能
5. **备份**:定期备份数据库和重要文件
6. **测试**:部署前运行完整的测试套件
7. **渐进式部署**:使用蓝绿部署或金丝雀发布
8. **文档化**:记录部署流程和配置
通过合理选择部署方式和遵循最佳实践,可以确保 Next.js 应用稳定、高效地运行在生产环境中。
服务端 · 2月17日 23:32