ReactServer ActionsNext.jsReact Server Components全端開發2026

React Server Actions:簡化全端開發的最佳實踐

React Server Actions 完整教學!Suspense Boundaries、useOptimistic、useFormStatus 實踐攻略。2026 年工程師必看的全端開發指南!

· 5 分鐘閱讀

React Server Components(RSC)和 Server Actions 是 React 生態系在 2024-2026 年最重要的架構演進。它們改變了「前後端」之間的邊界——伺服器不再只是 API 端點,而是可以直接參與 React 組件的渲染邏輯。對於前端工程師來說,這是一個需要重新思考「哪段代碼該在哪裡執行」的機會。讓我們來看看 2026 年的 Server Actions 最佳實踐。


Server Actions 是什麼

Server Actions 是在伺服器端執行的非同步函數,Client Components(客戶端組件)可以直接呼叫它們:

// app/actions.ts
'use server';

// 這個函數在伺服器執行
export async function submitForm(formData: FormData) {
  // 可以直接訪問資料庫
  const email = formData.get('email');
  const response = await db.insert({ email }).into('subscribers');
  return { success: true, id: response.id };
}
// app/page.tsx
'use client';

import { submitForm } from './actions';

export default function Form() {
  return (
    <form action={submitForm}>
      <input name="email" type="email" />
      <button type="submit">Subscribe</button>
    </form>
  );
}

這個模式消滅了 API 路由的 boilerplate——你不再需要寫 pages/api/subscribe.tsapp/api/subscribe/route.ts


為什麼 Server Actions 值得關注

減少 Boilerplate

傳統 API RoutesServer Actions
app/api/subscribe/route.ts直接在 actions.ts
POST handler + validation同一個函數
useEffect + fetch<form action={...}>
來回 HTTP 請求直接函數呼叫

類型安全

Server Actions 的參數和返回值可以完全享受 TypeScript 的類型檢查:

// app/actions.ts
'use server';

interface SubscribeResult {
  success: boolean;
  id?: string;
  error?: string;
}

export async function subscribe(
  email: string
): Promise<SubscribeResult> {
  if (!email.includes('@')) {
    return { success: false, error: 'Invalid email' };
  }

  const result = await db.insert({ email });
  return { success: true, id: result.id };
}
// 在 Client Component 中調用,類型自動推斷
const result = await subscribe(userInput);
if (result.error) {
  // TypeScript 知道 result.error 存在
  showError(result.error);
}

Suspense Boundaries:處理載入狀態

Server Actions 的一大挑戰是:伺服器執行意味著回應時間不確定。Suspense Boundaries 是 React 提供的解決方案。

基本用法

// app/page.tsx
import { Suspense } from 'react';
import { UserList } from './UserList';
import { UserListSkeleton } from './UserListSkeleton';

export default function Page() {
  return (
    <div>
      <h1>Users</h1>
      {/* 慢伺服器組件會觸發 loading UI */}
      <Suspense fallback={<UserListSkeleton />}>
        <UserList />
      </Suspense>
    </div>
  );
}

多層 Suspense

export default function Dashboard() {
  return (
    <div>
      {/* 每個區塊獨立的 Suspense */}
      <Suspense fallback={<ProfileSkeleton />}>
        <UserProfile />
      </Suspense>

      <Suspense fallback={<StatsSkeleton />}>
        <Stats />
      </Suspense>

      <Suspense fallback={<ActivitySkeleton />}>
        <RecentActivity />
      </Suspense>
    </div>
  );
}

每個 <Suspense> 邊界可以獨立載入,這讓頁面的感知效能大幅提升——用戶可以看到頁面「分塊」出現,而不是等整個頁面。


useOptimistic:樂觀更新

useOptimistic 是 React 19 引入的 Hook,專門用來處理「在伺服器回應前,先更新 UI」的場景。

典型場景:按讚

'use client';

import { useOptimistic, useTransition } from 'react';
import { toggleLike } from './actions';

type LikeButtonProps = {
  initialLikes: number;
  initialHasLiked: boolean;
  postId: string;
};

export function LikeButton({
  initialLikes,
  initialHasLiked,
  postId,
}: LikeButtonProps) {
  const [optimisticState, addOptimistic] = useOptimistic(
    { likes: initialLikes, hasLiked: initialHasLiked },
    // 樂觀更新函數
    (state, newHasLiked: boolean) => ({
      likes: state.likes + (newHasLiked ? 1 : -1),
      hasLiked: newHasLiked,
    })
  );

  const [isPending, startTransition] = useTransition();

  const handleClick = async () => {
    const newHasLiked = !optimisticState.hasLiked;

    // 立即更新 UI(樂觀)
    addOptimistic(newHasLiked);

    // 背景發送伺服器請求
    startTransition(async () => {
      await toggleLike(postId, newHasLiked);
    });
  };

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

用戶點擊按鈕的瞬間,UI 就更新了——不需要等伺服器回應。如果請求失敗,React 會自動回滾到正確狀態。

useFormStatus:表單提交狀態

useFormStatus 提供表單提交的狀態資訊,特別適合用在表單內部的提交按鈕:

// app/submit-button.tsx
'use client';

import { useFormStatus } from 'react-dom';

export function SubmitButton() {
  const { pending } = useFormStatus();

  return (
    <button type="submit" disabled={pending}>
      {pending ? '提交中...' : '提交'}
    </button>
  );
}
// app/form.tsx
import { SubmitButton } from './submit-button';

export function ContactForm() {
  return (
    <form action={async (formData) => {
      'use server';
      await sendEmail(formData);
    }}>
      <input name="email" type="email" />
      <textarea name="message" />
      {/* SubmitButton 可以讀取所屬表單的狀態 */}
      <SubmitButton />
    </form>
  );
}

安全性最佳實踐

Server Actions 的安全性容易被忽略,因為它看起來像普通的函數呼叫。以下是必須注意的事項:

1. 總是驗證用戶輸入

'use server';

export async function submitOrder(formData: FormData) {
  // ❌ 永遠不要信任 client 端的資料
  const userId = formData.get('userId'); // 可以被偽造

  // ✅ 從 session/cookie 獲取真實的 userId
  const session = await getSession();
  const userId = session.user.id;

  // ✅ 驗證所有輸入
  const productId = formData.get('productId');
  const quantity = parseInt(formData.get('quantity') as string, 10);

  if (quantity < 1 || quantity > 100) {
    return { error: 'Invalid quantity' };
  }

  // ✅ 授權檢查
  const product = await db.products.find(productId);
  if (!product) {
    return { error: 'Product not found' };
  }

  const order = await db.orders.create({
    userId: session.user.id,
    productId,
    quantity,
  });

  return { success: true, orderId: order.id };
}

2. 使用 Zod 進行 Schema 驗證

import { z } from 'zod';

const SubscribeSchema = z.object({
  email: z.string().email('Invalid email address'),
  name: z.string().min(2, 'Name too short').max(100),
});

'use server';

export async function subscribe(formData: FormData) {
  const rawData = {
    email: formData.get('email'),
    name: formData.get('name'),
  };

  // Zod 驗證 + TypeScript 類型推斷
  const result = SubscribeSchema.safeParse(rawData);

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

  const { email, name } = result.data;
  // ... 執行實際邏輯
}

3. Rate Limiting

import { ratelimit } from './ratelimit';

'use server';

export async function submitComment(formData: FormData) {
  const { success } = await ratelimit.limit('comments');

  if (!success) {
    return { error: 'Too many requests. Please wait.' };
  }

  // ... 處理評論提交
}

Server Actions vs API Routes:何時用哪個

場景Server Actions ✅API Routes
表單提交✅ 直接對應需要額外處理
資料庫 CRUD✅ 直接存取需要 API 層
第三方 API 呼叫
Webhook
需要 middleware
公開 API(供外部使用)

結語:重新思考前後端邊界

Server Actions 代表的不只是一個新語法,而是 React 生態系對「前後端分離」這個概念的重新思考。

過去,前端工程師寫 JavaScript,後端工程師寫 API,兩者之間有明確的邊界。Server Actions 模糊了這個邊界——你可以在 React 組件裡直接呼叫伺服器函數,不需要 HTTP 請求,不需要 useEffect

這個變化對前端工程師提出了新的要求:你不能再假設所有代碼都跑在瀏覽器裡。你需要理解伺服器環境的限制(不能訪問 windowdocument),也需要更嚴肅地對待安全性——攻擊者可以直接操控你的 Server Action 參數。

但這個變化也帶來了真正的簡化:更少的 boilerplate、更直接的資料流、更天然的 TypeScript 支援。


延伸閱讀

本文是「2026 React 進階技術」系列文章之一。