Post

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. 最小化客户端边界
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.