← 返回资讯
苏晴
资深编辑
已审核

Next.js 14 Server Actions 实战:告别 API Routes 的 5 个理由

Next.js 14 的 Server Actions 是我今年最喜欢的前端特性。

Next.js 14 Server Actions 实战:告别 API Routes 的 5 个理由

Next.js 14 Server Actions 实战:告别 API Routes 的 5 个理由

Next.js 14 的 Server Actions 是我今年最喜欢的前端特性。

过去写一个表单提交要这样:定义 API Route → 写 fetch 调用 → 处理 loading 状态 → 处理 error → 更新 UI。一套下来至少 4 个文件。

现在用 Server Actions,一个文件搞定。

什么是 Server Actions

Server Actions 就是可以在客户端直接调用的服务端函数。用 'use server' 标记。

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

import { revalidatePath } from 'next/cache'
import { db } from '@/lib/db'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  const content = formData.get('content') as string
  
  // 直接在服务端操作数据库
  await db.post.create({
    data: { title, content }
  })
  
  // 重新验证缓存
  revalidatePath('/posts')
}
TYPESCRIPT
// app/page.tsx
'use client'

import { createPost } from './actions'

export default function NewPostForm() {
  return (
    <form action={createPost}>
      <input name="title" required />
      <textarea name="content" required />
      <button type="submit">发布</button>
    </form>
  )
}

理由 1:代码量减少 60%

传统方式需要:

CODE
app/
  api/
    posts/
      route.ts        # API Route 定义
  components/
    NewPostForm.tsx   # 表单组件 + fetch + 状态管理
  types/
    post.ts           # 类型定义

Server Actions 方式:

CODE
app/
  actions.ts          # 所有服务端操作
  page.tsx            # 表单组件

对比一下代码量:

TYPESCRIPT
// 传统方式 - API Route
// app/api/posts/route.ts
export async function POST(request: Request) {
  try {
    const body = await request.json()
    const post = await db.post.create({ data: body })
    return Response.json(post, { status: 201 })
  } catch (error) {
    return Response.json({ error: '创建失败' }, { status: 500 })
  }
}

// 传统方式 - 前端调用
// components/NewPostForm.tsx
'use client'
export function NewPostForm() {
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState('')
  
  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault()
    setLoading(true)
    try {
      const res = await fetch('/api/posts', {
        method: 'POST',
        body: JSON.stringify({ title, content })
      })
      if (!res.ok) throw new Error('失败')
      router.refresh()
    } catch (err) {
      setError(err.message)
    } finally {
      setLoading(false)
    }
  }
  // ... JSX
}

// Server Actions 方式 - 一个文件搞定
// app/actions.ts
'use server'
export async function createPost(formData: FormData) {
  await db.post.create({
    data: {
      title: formData.get('title'),
      content: formData.get('content')
    }
  })
  revalidatePath('/posts')
}

理由 2:渐进增强

Server Actions 天然支持无 JS 运行。表单的 action 属性在 JS 禁用时依然能工作:

TYPESCRIPT
// 这个表单在 JS 禁用时依然能提交
<form action={createPost}>
  <input name="title" required />
  <button type="submit">提交</button>
</form>

配合 useFormStatus 还能拿到提交状态:

TYPESCRIPT
'use client'
import { useFormStatus } from 'react-dom'

function SubmitButton() {
  const { pending } = useFormStatus()
  return (
    <button type="submit" disabled={pending}>
      {pending ? '提交中...' : '提交'}
    </button>
  )
}

理由 3:类型安全

传统 API Route 需要自己定义请求/响应的类型,前端和后端的类型很容易不同步。Server Actions 天然共享类型:

TYPESCRIPT
// actions.ts
'use server'

interface CreatePostInput {
  title: string
  content: string
  tags: string[]
}

export async function createPost(input: CreatePostInput) {
  // input 的类型在前后端完全一致
  return await db.post.create({ data: input })
}

// page.tsx - 调用时自动有类型提示
import { createPost } from './actions'
// createPost({ title: '', ... }) // IDE 自动补全参数

理由 4:乐观更新更简单

TYPESCRIPT
'use client'
import { useOptimistic } from 'react'
import { createPost } from './actions'

export function PostList({ initialPosts }: { initialPosts: Post[] }) {
  const [posts, addOptimisticPost] = useOptimistic(
    initialPosts,
    (state, newPost: Post) => [newPost, ...state]
  )
  
  const handleCreate = async (formData: FormData) => {
    const newPost = {
      id: 'temp',
      title: formData.get('title') as string,
      content: formData.get('content') as string,
    }
    // 立即更新 UI
    addOptimisticPost(newPost)
    // 后台提交
    await createPost(formData)
  }
  
  return (
    <>
      <form action={handleCreate}>...</form>
      {posts.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
    </>
  )
}

理由 5:安全

Server Actions 默认是 POST 请求,自动处理 CSRF 保护:

TYPESCRIPT
// Next.js 自动为 Server Actions 添加 CSRF token
// 你不需要手动处理

// 还可以用 server-only 包确保代码只在服务端运行
import 'server-only'

export async function deleteUser(userId: string) {
  // 这个函数永远不会被打包到客户端代码中
  await db.user.delete({ where: { id: userId } })
}

注意事项

Server Actions 不是银弹:

1. 不适合 GET 请求 - 查询数据还是用 Server Components 或 API Routes

2. 不适合第三方调用 - 如果需要对外开放 API,还是用 API Routes

3. 序列化限制 - 参数和返回值必须可序列化(不能传函数、Class 实例等)

4. 调试困难 - 错误栈不直观,建议加详细日志

TYPESCRIPT
'use server'

export async function safeAction<T>(
  fn: () => Promise<T>,
  errorMessage: string
): Promise<{ data?: T; error?: string }> {
  try {
    const data = await fn()
    return { data }
  } catch (error) {
    console.error(`[Server Action] ${errorMessage}:`, error)
    return { error: errorMessage }
  }
}

// 使用
const result = await safeAction(
  () => db.post.create({ data: input }),
  '创建文章失败'
)

总结

Server Actions 让全栈开发回到了"简单"的轨道上。如果你在用 Next.js 14+,强烈建议试试。

219
3654 阅读
4 评论
分享
链接已复制
编辑说明

本文由 MakeSense 编辑团队撰写并审核。文中引用的数据和观点均经过交叉验证,如有疏漏欢迎在评论区指正。最后更新:2026年07月12日 11:21

苏晴

资深编辑

科技媒体从业 8 年,曾就职于多家科技媒体。关注 AI 创业和投资赛道,采访过 50+ 位行业从业者。

读者评论 4

技术小白 1周前
作为非技术人员也看懂了,感谢作者的通俗讲解。
回复 点赞 (3)
Dev小王 1周前
终于有人把这个说清楚了,收藏了。
回复 点赞 (8)
A
AI研究员 1周前
观点有道理,不过我觉得还需要考虑算力成本的问题。
回复 点赞 (11)
M
创业者Mark 2天前
正在做相关方向,这篇文章给了我不少启发。
回复 点赞 (7)