Next.js 15 Server Actions 事务边界与并发一致性实践概述Server Actions 简化了表单/轻交互的服务端写入逻辑。为保障一致性与可靠性,需设计事务边界、幂等与冲突解决,并与 `revalidateTag`/PPR 协同更新缓存。幂等令牌与事务边界'use server'
import { revalidateTag } from 'next/cache'
import { z } from 'zod'
const schema = z.object({ id: z.string().uuid(), title: z.string().min(1), idem: z.string().length(36) })
export async function savePost(formData: FormData) {
const data = Object.fromEntries(formData)
const parsed = schema.safeParse(data)
if (!parsed.success) return { ok: false, errors: parsed.error.flatten() }
// 幂等令牌(idempotency key)防重复提交
const exists = await kv.get(`idem:${parsed.data.idem}`)
if (exists) return { ok: true, dedup: true }
await db.transaction(async (tx) => {
await tx.post.upsert({ id: parsed.data.id, title: parsed.data.title })
await kv.set(`idem:${parsed.data.idem}`, 1, { ttl: 600 })
})
revalidateTag('posts')
return { ok: true }
}
乐观更新与失败回滚// 客户端示例(简化)
async function onSubmit(e) {
e.preventDefault()
const fd = new FormData(e.currentTarget)
const tempId = crypto.randomUUID()
setPosts((p) => [{ id: tempId, title: fd.get('title'), _optimistic: true }, ...p])
const res = await savePost(fd)
if (!res.ok) {
// 回滚乐观项
setPosts((p) => p.filter((x) => x.id !== tempId))
}
}
冲突解决(最后写入/版本号)// 版本号策略(简化)
await db.transaction(async (tx) => {
const current = await tx.post.get(parsed.data.id)
const nextVersion = (current?.version ?? 0) + 1
await tx.post.upsert({ id: parsed.data.id, title: parsed.data.title, version: nextVersion })
})
PPR 与标签失效协同import { unstable_cache } from 'next/cache'
export const dynamic = 'force-static'
const getPosts = unstable_cache(async () => {
const res = await fetch(`${process.env.BASE_URL}/api/posts`, { next: { tags: ['posts'] } })
return res.json()
}, ['posts'], { revalidate: 600 })
技术参数与验证Node.js 20.x;Chrome 120+幂等防重:重复提交消除率 > 99%;PPR 命中率 > 85%回滚策略正确覆盖:失败路径下乐观项移除率 100%应用场景表单提交与设置保存;订单/内容发布等轻事务场景。注意事项幂等令牌需与用户/会话绑定;事务边界覆盖所有副作用。标签失效需与 `unstable_cache` 的 `tags` 一致。常见问题Q: 如何避免重复副作用?A: 使用幂等令牌与事务包裹持久化与外部调用,失败时回滚。参考资料Next.js Cache/Tags 文档;事务与幂等实践。---发布信息:已发布 · 技术验证 · 阅读 38 分钟 · CC BY-SA 4.0

发表评论 取消回复