react-server-components

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React Server Components

React Server Components

Build performant applications with React Server Components and Next.js App Router.
使用React Server Components和Next.js App Router构建高性能应用。

Core Workflow

核心工作流程

  1. Understand RSC model: Server vs client components
  2. Design component tree: Plan server/client boundaries
  3. Implement data fetching: Fetch in server components
  4. Add interactivity: Client components where needed
  5. Enable streaming: Suspense for progressive loading
  6. Optimize: Minimize client bundle size
  1. 理解RSC模型:服务端组件 vs 客户端组件
  2. 设计组件树:规划服务端/客户端边界
  3. 实现数据获取:在服务端组件中进行数据获取
  4. 添加交互性:在需要的地方使用客户端组件
  5. 启用流式传输:使用Suspense实现渐进式加载
  6. 优化:最小化客户端包体积

Server vs Client Components

服务端组件 vs 客户端组件

Key Differences

关键差异

FeatureServer ComponentsClient Components
RenderingServer onlyServer + Client
Bundle sizeZero JS sentAdds to bundle
Data fetchingDirect DB/API accessuseEffect/TanStack Query
State/EffectsNo hooksFull React hooks
Event handlersNoYes
Browser APIsNoYes
File directiveDefault (none)
'use client'
特性服务端组件客户端组件
渲染方式仅服务端服务端 + 客户端
包体积不发送任何JS会增加客户端包体积
数据获取直接访问数据库/API使用useEffect/TanStack Query
状态/副作用不支持Hooks支持完整React Hooks
事件处理器不支持支持
浏览器API不支持支持
文件指令默认(无)
'use client'

When to Use Each

适用场景

Server Components (Default)
  • Static content
  • Data fetching
  • Backend resource access
  • Large dependencies (markdown, syntax highlighting)
  • Sensitive logic/tokens
Client Components
  • Interactive UI (onClick, onChange)
  • useState, useEffect, useReducer
  • Browser APIs (localStorage, geolocation)
  • Custom hooks with state
  • Third-party client libraries
服务端组件(默认)
  • 静态内容
  • 数据获取
  • 后端资源访问
  • 大型依赖库(如markdown、语法高亮)
  • 敏感逻辑/令牌
客户端组件
  • 交互式UI(onClick、onChange等)
  • 使用useState、useEffect、useReducer
  • 浏览器API(localStorage、地理定位)
  • 带状态的自定义Hooks
  • 第三方客户端库

Basic Patterns

基础模式

Server Component (Default)

服务端组件(默认)

tsx
// app/users/page.tsx (Server Component - no directive needed)
import { db } from '@/lib/db';

async function getUsers() {
  return db.user.findMany({
    orderBy: { createdAt: 'desc' },
  });
}

export default async function UsersPage() {
  const users = await getUsers();

  return (
    <div>
      <h1>Users</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}
tsx
// app/users/page.tsx (Server Component - no directive needed)
import { db } from '@/lib/db';

async function getUsers() {
  return db.user.findMany({
    orderBy: { createdAt: 'desc' },
  });
}

export default async function UsersPage() {
  const users = await getUsers();

  return (
    <div>
      <h1>Users</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

Client Component

客户端组件

tsx
// components/Counter.tsx
'use client';

import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}
tsx
// components/Counter.tsx
'use client';

import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

Composition Pattern

组件组合模式

tsx
// app/dashboard/page.tsx (Server Component)
import { db } from '@/lib/db';
import { DashboardClient } from './DashboardClient';
import { StatsCard } from '@/components/StatsCard';

async function getStats() {
  const [users, orders, revenue] = await Promise.all([
    db.user.count(),
    db.order.count(),
    db.order.aggregate({ _sum: { total: true } }),
  ]);
  return { users, orders, revenue: revenue._sum.total };
}

export default async function DashboardPage() {
  const stats = await getStats();

  return (
    <div>
      {/* Server-rendered static content */}
      <h1>Dashboard</h1>

      {/* Server components with data */}
      <div className="grid grid-cols-3 gap-4">
        <StatsCard title="Users" value={stats.users} />
        <StatsCard title="Orders" value={stats.orders} />
        <StatsCard title="Revenue" value={`$${stats.revenue}`} />
      </div>

      {/* Client component for interactivity */}
      <DashboardClient initialData={stats} />
    </div>
  );
}
tsx
// app/dashboard/DashboardClient.tsx
'use client';

import { useState } from 'react';
import { DateRangePicker } from '@/components/DateRangePicker';
import { Chart } from '@/components/Chart';

interface DashboardClientProps {
  initialData: Stats;
}

export function DashboardClient({ initialData }: DashboardClientProps) {
  const [dateRange, setDateRange] = useState({ from: null, to: null });

  return (
    <div>
      <DateRangePicker value={dateRange} onChange={setDateRange} />
      <Chart data={initialData} />
    </div>
  );
}
tsx
// app/dashboard/page.tsx (Server Component)
import { db } from '@/lib/db';
import { DashboardClient } from './DashboardClient';
import { StatsCard } from '@/components/StatsCard';

async function getStats() {
  const [users, orders, revenue] = await Promise.all([
    db.user.count(),
    db.order.count(),
    db.order.aggregate({ _sum: { total: true } }),
  ]);
  return { users, orders, revenue: revenue._sum.total };
}

export default async function DashboardPage() {
  const stats = await getStats();

  return (
    <div>
      {/* 服务端渲染的静态内容 */}
      <h1>Dashboard</h1>

      {/* 带数据的服务端组件 */}
      <div className="grid grid-cols-3 gap-4">
        <StatsCard title="Users" value={stats.users} />
        <StatsCard title="Orders" value={stats.orders} />
        <StatsCard title="Revenue" value={`$${stats.revenue}`} />
      </div>

      {/* 用于交互的客户端组件 */}
      <DashboardClient initialData={stats} />
    </div>
  );
}
tsx
// app/dashboard/DashboardClient.tsx
'use client';

import { useState } from 'react';
import { DateRangePicker } from '@/components/DateRangePicker';
import { Chart } from '@/components/Chart';

interface DashboardClientProps {
  initialData: Stats;
}

export function DashboardClient({ initialData }: DashboardClientProps) {
  const [dateRange, setDateRange] = useState({ from: null, to: null });

  return (
    <div>
      <DateRangePicker value={dateRange} onChange={setDateRange} />
      <Chart data={initialData} />
    </div>
  );
}

Data Fetching Patterns

数据获取模式

Parallel Data Fetching

并行数据获取

tsx
// app/page.tsx
async function getUser(id: string) {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

async function getPosts(userId: string) {
  const res = await fetch(`/api/users/${userId}/posts`);
  return res.json();
}

async function getComments(userId: string) {
  const res = await fetch(`/api/users/${userId}/comments`);
  return res.json();
}

export default async function ProfilePage({ params }: { params: { id: string } }) {
  // Parallel fetching - all requests start simultaneously
  const [user, posts, comments] = await Promise.all([
    getUser(params.id),
    getPosts(params.id),
    getComments(params.id),
  ]);

  return (
    <div>
      <UserHeader user={user} />
      <PostsList posts={posts} />
      <CommentsList comments={comments} />
    </div>
  );
}
tsx
// app/page.tsx
async function getUser(id: string) {
  const res = await fetch(`/api/users/${id}`);
  return res.json();
}

async function getPosts(userId: string) {
  const res = await fetch(`/api/users/${userId}/posts`);
  return res.json();
}

async function getComments(userId: string) {
  const res = await fetch(`/api/users/${userId}/comments`);
  return res.json();
}

export default async function ProfilePage({ params }: { params: { id: string } }) {
  // 并行获取 - 所有请求同时发起
  const [user, posts, comments] = await Promise.all([
    getUser(params.id),
    getPosts(params.id),
    getComments(params.id),
  ]);

  return (
    <div>
      <UserHeader user={user} />
      <PostsList posts={posts} />
      <CommentsList comments={comments} />
    </div>
  );
}

Sequential Data Fetching (When Dependent)

串行数据获取(存在依赖时)

tsx
export default async function OrderPage({ params }: { params: { id: string } }) {
  // Sequential - second fetch depends on first
  const order = await getOrder(params.id);
  const customer = await getCustomer(order.customerId);

  return (
    <div>
      <OrderDetails order={order} />
      <CustomerInfo customer={customer} />
    </div>
  );
}
tsx
export default async function OrderPage({ params }: { params: { id: string } }) {
  // 串行获取 - 第二个请求依赖第一个请求的结果
  const order = await getOrder(params.id);
  const customer = await getCustomer(order.customerId);

  return (
    <div>
      <OrderDetails order={order} />
      <CustomerInfo customer={customer} />
    </div>
  );
}

Caching and Revalidation

缓存与重新验证

tsx
// Static data (cached indefinitely)
async function getStaticData() {
  const res = await fetch('https://api.example.com/static', {
    cache: 'force-cache', // Default
  });
  return res.json();
}

// Dynamic data (no cache)
async function getDynamicData() {
  const res = await fetch('https://api.example.com/dynamic', {
    cache: 'no-store',
  });
  return res.json();
}

// Revalidate after time
async function getRevalidatedData() {
  const res = await fetch('https://api.example.com/data', {
    next: { revalidate: 3600 }, // Revalidate every hour
  });
  return res.json();
}

// Revalidate by tag
async function getTaggedData() {
  const res = await fetch('https://api.example.com/products', {
    next: { tags: ['products'] },
  });
  return res.json();
}

// Trigger revalidation
import { revalidateTag } from 'next/cache';
revalidateTag('products');
tsx
// 静态数据(永久缓存)
async function getStaticData() {
  const res = await fetch('https://api.example.com/static', {
    cache: 'force-cache', // 默认值
  });
  return res.json();
}

// 动态数据(无缓存)
async function getDynamicData() {
  const res = await fetch('https://api.example.com/dynamic', {
    cache: 'no-store',
  });
  return res.json();
}

// 定时重新验证
async function getRevalidatedData() {
  const res = await fetch('https://api.example.com/data', {
    next: { revalidate: 3600 }, // 每小时重新验证一次
  });
  return res.json();
}

// 按标签重新验证
async function getTaggedData() {
  const res = await fetch('https://api.example.com/products', {
    next: { tags: ['products'] },
  });
  return res.json();
}

// 触发重新验证
import { revalidateTag } from 'next/cache';
revalidateTag('products');

Streaming with Suspense

使用Suspense实现流式传输

Page-Level Streaming

页面级流式传输

tsx
// app/dashboard/page.tsx
import { Suspense } from 'react';
import { StatsCards, StatsCardsSkeleton } from './StatsCards';
import { RecentOrders, RecentOrdersSkeleton } from './RecentOrders';
import { TopProducts, TopProductsSkeleton } from './TopProducts';

export default function DashboardPage() {
  return (
    <div>
      <h1>Dashboard</h1>

      {/* Each section streams independently */}
      <Suspense fallback={<StatsCardsSkeleton />}>
        <StatsCards />
      </Suspense>

      <div className="grid grid-cols-2 gap-4 mt-8">
        <Suspense fallback={<RecentOrdersSkeleton />}>
          <RecentOrders />
        </Suspense>

        <Suspense fallback={<TopProductsSkeleton />}>
          <TopProducts />
        </Suspense>
      </div>
    </div>
  );
}
tsx
// app/dashboard/page.tsx
import { Suspense } from 'react';
import { StatsCards, StatsCardsSkeleton } from './StatsCards';
import { RecentOrders, RecentOrdersSkeleton } from './RecentOrders';
import { TopProducts, TopProductsSkeleton } from './TopProducts';

export default function DashboardPage() {
  return (
    <div>
      <h1>Dashboard</h1>

      {/* 每个区块独立流式传输 */}
      <Suspense fallback={<StatsCardsSkeleton />}>
        <StatsCards />
      </Suspense>

      <div className="grid grid-cols-2 gap-4 mt-8">
        <Suspense fallback={<RecentOrdersSkeleton />}>
          <RecentOrders />
        </Suspense>

        <Suspense fallback={<TopProductsSkeleton />}>
          <TopProducts />
        </Suspense>
      </div>
    </div>
  );
}

Component with Data

带数据的组件

tsx
// app/dashboard/StatsCards.tsx
import { db } from '@/lib/db';

async function getStats() {
  // Simulate slow query
  await new Promise((resolve) => setTimeout(resolve, 2000));
  return db.stats.findFirst();
}

export async function StatsCards() {
  const stats = await getStats();

  return (
    <div className="grid grid-cols-4 gap-4">
      <Card title="Revenue" value={stats.revenue} />
      <Card title="Orders" value={stats.orders} />
      <Card title="Customers" value={stats.customers} />
      <Card title="Products" value={stats.products} />
    </div>
  );
}

export function StatsCardsSkeleton() {
  return (
    <div className="grid grid-cols-4 gap-4">
      {[...Array(4)].map((_, i) => (
        <div key={i} className="h-24 bg-gray-200 animate-pulse rounded" />
      ))}
    </div>
  );
}
tsx
// app/dashboard/StatsCards.tsx
import { db } from '@/lib/db';

async function getStats() {
  // 模拟慢查询
  await new Promise((resolve) => setTimeout(resolve, 2000));
  return db.stats.findFirst();
}

export async function StatsCards() {
  const stats = await getStats();

  return (
    <div className="grid grid-cols-4 gap-4">
      <Card title="Revenue" value={stats.revenue} />
      <Card title="Orders" value={stats.orders} />
      <Card title="Customers" value={stats.customers} />
      <Card title="Products" value={stats.products} />
    </div>
  );
}

export function StatsCardsSkeleton() {
  return (
    <div className="grid grid-cols-4 gap-4">
      {[...Array(4)].map((_, i) => (
        <div key={i} className="h-24 bg-gray-200 animate-pulse rounded" />
      ))}
    </div>
  );
}

Loading States

加载状态

tsx
// app/products/loading.tsx
export default function ProductsLoading() {
  return (
    <div className="grid grid-cols-3 gap-4">
      {[...Array(9)].map((_, i) => (
        <div key={i} className="h-64 bg-gray-200 animate-pulse rounded" />
      ))}
    </div>
  );
}
tsx
// app/products/loading.tsx
export default function ProductsLoading() {
  return (
    <div className="grid grid-cols-3 gap-4">
      {[...Array(9)].map((_, i) => (
        <div key={i} className="h-64 bg-gray-200 animate-pulse rounded" />
      ))}
    </div>
  );
}

Server Actions

Server Actions

Form Actions

表单操作

tsx
// app/contact/page.tsx
import { submitContact } from './actions';

export default function ContactPage() {
  return (
    <form action={submitContact}>
      <input name="email" type="email" required />
      <textarea name="message" required />
      <button type="submit">Send</button>
    </form>
  );
}
tsx
// app/contact/actions.ts
'use server';

import { z } from 'zod';
import { revalidatePath } from 'next/cache';

const schema = z.object({
  email: z.string().email(),
  message: z.string().min(10),
});

export async function submitContact(formData: FormData) {
  const validatedFields = schema.safeParse({
    email: formData.get('email'),
    message: formData.get('message'),
  });

  if (!validatedFields.success) {
    return { error: validatedFields.error.flatten().fieldErrors };
  }

  await db.contact.create({
    data: validatedFields.data,
  });

  revalidatePath('/contacts');
  return { success: true };
}
tsx
// app/contact/page.tsx
import { submitContact } from './actions';

export default function ContactPage() {
  return (
    <form action={submitContact}>
      <input name="email" type="email" required />
      <textarea name="message" required />
      <button type="submit">Send</button>
    </form>
  );
}
tsx
// app/contact/actions.ts
'use server';

import { z } from 'zod';
import { revalidatePath } from 'next/cache';

const schema = z.object({
  email: z.string().email(),
  message: z.string().min(10),
});

export async function submitContact(formData: FormData) {
  const validatedFields = schema.safeParse({
    email: formData.get('email'),
    message: formData.get('message'),
  });

  if (!validatedFields.success) {
    return { error: validatedFields.error.flatten().fieldErrors };
  }

  await db.contact.create({
    data: validatedFields.data,
  });

  revalidatePath('/contacts');
  return { success: true };
}

With useActionState

结合useActionState

tsx
// components/ContactForm.tsx
'use client';

import { useActionState } from 'react';
import { submitContact } from '@/app/contact/actions';

export function ContactForm() {
  const [state, formAction, isPending] = useActionState(submitContact, null);

  return (
    <form action={formAction}>
      <input name="email" type="email" required />
      {state?.error?.email && <p className="text-red-500">{state.error.email}</p>}

      <textarea name="message" required />
      {state?.error?.message && <p className="text-red-500">{state.error.message}</p>}

      <button type="submit" disabled={isPending}>
        {isPending ? 'Sending...' : 'Send'}
      </button>

      {state?.success && <p className="text-green-500">Message sent!</p>}
    </form>
  );
}
tsx
// components/ContactForm.tsx
'use client';

import { useActionState } from 'react';
import { submitContact } from '@/app/contact/actions';

export function ContactForm() {
  const [state, formAction, isPending] = useActionState(submitContact, null);

  return (
    <form action={formAction}>
      <input name="email" type="email" required />
      {state?.error?.email && <p className="text-red-500">{state.error.email}</p>}

      <textarea name="message" required />
      {state?.error?.message && <p className="text-red-500">{state.error.message}</p>}

      <button type="submit" disabled={isPending}>
        {isPending ? 'Sending...' : 'Send'}
      </button>

      {state?.success && <p className="text-green-500">Message sent!</p>}
    </form>
  );
}

Optimistic Updates

乐观更新

tsx
// components/LikeButton.tsx
'use client';

import { useOptimistic, useTransition } from 'react';
import { likePost } from '@/app/actions';

interface LikeButtonProps {
  postId: string;
  initialLikes: number;
  isLiked: boolean;
}

export function LikeButton({ postId, initialLikes, isLiked }: LikeButtonProps) {
  const [isPending, startTransition] = useTransition();
  const [optimisticState, addOptimistic] = useOptimistic(
    { likes: initialLikes, isLiked },
    (state, newIsLiked: boolean) => ({
      likes: newIsLiked ? state.likes + 1 : state.likes - 1,
      isLiked: newIsLiked,
    })
  );

  const handleClick = () => {
    startTransition(async () => {
      addOptimistic(!optimisticState.isLiked);
      await likePost(postId);
    });
  };

  return (
    <button onClick={handleClick} disabled={isPending}>
      {optimisticState.isLiked ? '❤️' : '🤍'} {optimisticState.likes}
    </button>
  );
}
tsx
// components/LikeButton.tsx
'use client';

import { useOptimistic, useTransition } from 'react';
import { likePost } from '@/app/actions';

interface LikeButtonProps {
  postId: string;
  initialLikes: number;
  isLiked: boolean;
}

export function LikeButton({ postId, initialLikes, isLiked }: LikeButtonProps) {
  const [isPending, startTransition] = useTransition();
  const [optimisticState, addOptimistic] = useOptimistic(
    { likes: initialLikes, isLiked },
    (state, newIsLiked: boolean) => ({
      likes: newIsLiked ? state.likes + 1 : state.likes - 1,
      isLiked: newIsLiked,
    })
  );

  const handleClick = () => {
    startTransition(async () => {
      addOptimistic(!optimisticState.isLiked);
      await likePost(postId);
    });
  };

  return (
    <button onClick={handleClick} disabled={isPending}>
      {optimisticState.isLiked ? '❤️' : '🤍'} {optimisticState.likes}
    </button>
  );
}

Patterns for Common Use Cases

常见场景模式

Authentication Check

身份验证检查

tsx
// app/dashboard/layout.tsx
import { redirect } from 'next/navigation';
import { getSession } from '@/lib/auth';

export default async function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const session = await getSession();

  if (!session) {
    redirect('/login');
  }

  return (
    <div>
      <nav>Welcome, {session.user.name}</nav>
      {children}
    </div>
  );
}
tsx
// app/dashboard/layout.tsx
import { redirect } from 'next/navigation';
import { getSession } from '@/lib/auth';

export default async function DashboardLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const session = await getSession();

  if (!session) {
    redirect('/login');
  }

  return (
    <div>
      <nav>Welcome, {session.user.name}</nav>
      {children}
    </div>
  );
}

Passing Server Data to Client

服务端数据传递到客户端

tsx
// app/products/[id]/page.tsx
import { getProduct } from '@/lib/products';
import { ProductGallery } from './ProductGallery';

export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await getProduct(params.id);

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>

      {/* Pass serializable data to client component */}
      <ProductGallery
        images={product.images}
        initialIndex={0}
      />
    </div>
  );
}
tsx
// app/products/[id]/page.tsx
import { getProduct } from '@/lib/products';
import { ProductGallery } from './ProductGallery';

export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await getProduct(params.id);

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>

      {/* 向客户端组件传递可序列化数据 */}
      <ProductGallery
        images={product.images}
        initialIndex={0}
      />
    </div>
  );
}

Nested Client Components

嵌套客户端组件

tsx
// components/Modal.tsx
'use client';

import { createContext, useContext, useState } from 'react';

const ModalContext = createContext<{
  open: boolean;
  setOpen: (open: boolean) => void;
} | null>(null);

export function ModalProvider({ children }: { children: React.ReactNode }) {
  const [open, setOpen] = useState(false);
  return (
    <ModalContext.Provider value={{ open, setOpen }}>
      {children}
    </ModalContext.Provider>
  );
}

export function useModal() {
  const context = useContext(ModalContext);
  if (!context) throw new Error('useModal must be used within ModalProvider');
  return context;
}
tsx
// components/Modal.tsx
'use client';

import { createContext, useContext, useState } from 'react';

const ModalContext = createContext<{
  open: boolean;
  setOpen: (open: boolean) => void;
} | null>(null);

export function ModalProvider({ children }: { children: React.ReactNode }) {
  const [open, setOpen] = useState(false);
  return (
    <ModalContext.Provider value={{ open, setOpen }}>
      {children}
    </ModalContext.Provider>
  );
}

export function useModal() {
  const context = useContext(ModalContext);
  if (!context) throw new Error('useModal must be used within ModalProvider');
  return context;
}

Best Practices

最佳实践

  1. Start with Server Components: Default to server, add 'use client' only when needed
  2. Push client boundaries down: Keep interactivity in leaf components
  3. Parallel fetch data: Use Promise.all for independent data
  4. Use Suspense for streaming: Progressive loading for better UX
  5. Colocate data fetching: Fetch where data is used
  6. Minimize client bundle: Heavy libraries stay on server
  7. Cache appropriately: Use revalidation strategies
  8. Handle errors: Use error.tsx boundaries
  1. 优先使用服务端组件:默认使用服务端组件,仅在需要时添加'use client'指令
  2. 下移客户端边界:将交互逻辑放在叶子组件中
  3. 并行获取数据:对独立数据使用Promise.all
  4. 使用Suspense实现流式传输:通过渐进式加载提升用户体验
  5. 就近获取数据:在使用数据的组件中进行数据获取
  6. 最小化客户端包体积:将大型依赖库保留在服务端
  7. 合理使用缓存:采用合适的重新验证策略
  8. 处理错误:使用error.tsx错误边界
  9. 在布局中进行身份验证检查
  10. 最小化客户端JavaScript代码

Output Checklist

输出检查清单

Every RSC implementation should include:
  • Server components for data fetching
  • 'use client' only where needed
  • Suspense boundaries for streaming
  • Loading skeletons for each section
  • Parallel data fetching with Promise.all
  • Server actions for mutations
  • Proper caching strategy
  • Error boundaries (error.tsx)
  • Authentication checks in layouts
  • Minimal client JavaScript bundle
每个RSC实现都应包含以下内容:
  • 用于数据获取的服务端组件
  • 仅在必要处使用'use client'指令
  • 用于流式传输的Suspense边界
  • 每个区块的加载骨架屏
  • 使用Promise.all进行并行数据获取
  • 用于数据变更的Server Actions
  • 合适的缓存策略
  • 错误边界(error.tsx)
  • 布局中的身份验证检查
  • 最小化的客户端JavaScript包体积