Next.js 一些使用和项目性能优化
Next.js 一些使用和项目性能优化
Server Components 和 Client Components
Next.js 的组件架构。
Server Components(服务器组件)
- 在服务器端渲染,HTML 直接发送给客户端
- 可以直接访问数据库、文件系统等服务器资源
- 不包含 JavaScript 交互逻辑
- 默认情况下,App Router 中的所有组件都是 Server Components
Client Components(客户端组件)
- 在浏览器中渲染和执行
- 可以使用 React hooks、事件处理器、浏览器 API
- 需要 JavaScript bundle 才能工作
- 必须显式标记为客户端组件
“use client” 指令详解
何时使用 “use client”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"use client";
import { useState, useEffect } from "react";
export default function InteractiveCounter() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
return (
<div>
<p>当前计数: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
);
}
使用场景:
- 需要使用 React hooks(useState、useEffect 等)
- 需要事件处理器(onClick、onChange 等)
- 需要访问浏览器 API(localStorage、geolocation 等)
- 需要使用第三方交互库
“use client” 最佳实践
- 最小化客户端边界
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ❌ 不好的做法 - 整个页面都是客户端组件
"use client";
export default function Page() {
const [count, setCount] = useState(0);
return (
<div>
<StaticHeader />
<Counter count={count} setCount={setCount} />
<StaticFooter />
</div>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ✅ 好的做法 - 只有需要交互的组件是客户端组件
export default function Page() {
return (
<div>
<StaticHeader />
<InteractiveCounter />
<StaticFooter />
</div>
);
}
// 单独的客户端组件
("use client");
function InteractiveCounter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>计数: {count}</button>;
}
“use server” 指令详解
Server Actions 基础
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// app/actions.js
"use server";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
export async function createPost(formData) {
// 服务器端数据处理
const title = formData.get("title");
const content = formData.get("content");
// 数据库操作
const post = await db.post.create({
data: { title, content },
});
// 重新验证缓存
revalidatePath("/posts");
// 重定向到新创建的文章
redirect(`/posts/${post.id}`);
}
在表单中使用 Server Actions
1
2
3
4
5
6
7
8
9
10
11
12
// app/create-post/page.js
import { createPost } from "../actions";
export default function CreatePost() {
return (
<form action={createPost}>
<input name="title" placeholder="文章标题" required />
<textarea name="content" placeholder="文章内容" required />
<button type="submit">创建文章</button>
</form>
);
}
在客户端组件中使用 Server Actions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
"use client";
import { createPost } from "../actions";
import { useFormStatus } from "react-dom";
export default function CreatePostForm() {
return (
<form action={createPost}>
<input name="title" placeholder="文章标题" required />
<textarea name="content" placeholder="文章内容" required />
<SubmitButton />
</form>
);
}
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "创建中..." : "创建文章"}
</button>
);
}
项目优化策略
1. 组件架构优化
分层设计原则:
1
2
3
4
5
页面层 (Server Component)
├── 布局组件 (Server Component)
├── 数据获取组件 (Server Component)
└── 交互组件 (Client Component)
└── 子交互组件 (继承Client状态)
2. 数据获取优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 服务器组件中的数据获取
export default async function PostsPage() {
// 并行数据获取
const [posts, categories] = await Promise.all([
fetchPosts(),
fetchCategories(),
]);
return (
<div>
<PostsList posts={posts} />
<CategoriesFilter categories={categories} />
</div>
);
}
3. 缓存策略优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 使用不同的缓存策略
export async function fetchPosts() {
const res = await fetch("https://api.example.com/posts", {
// 静态数据 - 构建时获取
cache: "force-cache",
});
return res.json();
}
export async function fetchUserPosts(userId) {
const res = await fetch(`https://api.example.com/users/${userId}/posts`, {
// 动态数据 - 每次请求都获取
cache: "no-store",
});
return res.json();
}
export async function fetchPopularPosts() {
const res = await fetch("https://api.example.com/posts/popular", {
// 定时重新验证 - 每小时更新一次
next: { revalidate: 3600 },
});
return res.json();
}
4. Bundle 大小优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 动态导入大型客户端组件
import dynamic from "next/dynamic";
const HeavyChart = dynamic(() => import("./HeavyChart"), {
loading: () => <p>图表加载中...</p>,
ssr: false, // 仅在客户端渲染
});
export default function Dashboard() {
return (
<div>
<StaticDashboardHeader />
<HeavyChart />
</div>
);
}
5. 性能监控和调试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 使用Suspense边界处理加载状态
import { Suspense } from "react";
export default function PostsPage() {
return (
<div>
<h1>最新文章</h1>
<Suspense fallback={<PostsSkeleton />}>
<PostsList />
</Suspense>
</div>
);
}
async function PostsList() {
const posts = await fetchPosts();
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
常见错误和解决方案
1. 在 Server Component 中使用客户端 API
1
2
3
4
5
6
7
8
9
10
11
12
// ❌ 错误 - 在服务器组件中使用useState
export default function BadComponent() {
const [state, setState] = useState(0) // 报错!
return <div>{state}</div>
}
// ✅ 正确 - 添加"use client"指令
'use client'
export default function GoodComponent() {
const [state, setState] = useState(0)
return <div>{state}</div>
}
2. 过度使用”use client”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ❌ 避免 - 不必要的客户端组件
'use client'
export default function Page() {
return (
<div>
<h1>静态标题</h1>
<p>静态内容</p>
</div>
)
}
// ✅ 改进 - 保持为服务器组件
export default function Page() {
return (
<div>
<h1>静态标题</h1>
<p>静态内容</p>
</div>
)
}
This post is licensed under CC BY 4.0 by the author.