고민보단 실천을

Next.js Server Actions 실전: 폼 처리, 에러 처리, 중복 제출 방지 패턴 본문

카테고리 없음

Next.js Server Actions 실전: 폼 처리, 에러 처리, 중복 제출 방지 패턴

Just-Do-It 2026. 3. 4. 13:59

Next.js Server Actions 실전: 폼 처리, 에러 처리, 중복 제출 방지 패턴

Server Actions는 API 라우트 없이 서버에서 mutation을 처리할 수 있어 검색 수요가 빠르게 늘고 있습니다. 실무에서 중요한 건 폼 에러를 안정적으로 보여주고, 중복 제출을 막고, 권한 체크를 서버에서 확실히 하는 것입니다.

Next.js Server Actions 실전: 폼 처리, 에러 처리, 중복 제출 방지 패턴
서버 액션은 API와 동일한 수준으로 검증/권한/중복 처리를 설계해야 합니다.

옵션/핵심 요소(3~6개)

항목의미언제 쓰는지(실무 상황)
'use server'서버 실행 표시DB 접근/비밀키 사용을 action으로 이동
서버 검증최종 입력 검증클라 검증은 UX, 서버 검증은 보안/정합성
useActionState에러/상태 관리필드 에러/전역 에러를 안정적으로 렌더링
중복 제출 방지동시 실행 제어버튼 disable + 서버 idempotency 조합
권한 체크인증/인가action도 API처럼 권한 체크가 필요

예시 코드

// app/posts/[id]/actions.ts
'use server'

type ActionState = { ok: boolean; message?: string }

export async function createComment(prev: ActionState, formData: FormData): Promise<ActionState> {
const postId = String(formData.get('postId') || '')
const body = String(formData.get('body') || '')
if (!postId || body.trim().length < 2) return { ok: false, message: '댓글을 2자 이상 입력하세요.' }
// TODO: 인증/인가 체크
// TODO: DB 저장
return { ok: true }
}

// app/posts/[id]/comment-form.tsx
import { useActionState } from 'react'
import { createComment } from './actions'

export function CommentForm({ postId }: { postId: string }) {
const [state, action, pending] = useActionState(createComment, { ok: false })
return (
<form action={action}>
<input type='hidden' name='postId' value={postId} />
<textarea name='body' />
<button type='submit' disabled={pending}>등록</button>
{state.message ? <p>{state.message}</p> : null}
</form>
)
}

문제 상황(정확히 1개)

상황: 버튼을 두 번 누르면 동일 요청이 두 번 처리되어 데이터가 2번 생성된다

원인: 클라이언트 disable만으로는 네트워크 재시도/중복 요청을 완전히 막지 못하고, 서버가 idempotency를 제공하지 않는다

해결: 클라이언트는 pending 동안 submit을 막고, 서버는 유니크 제약 또는 idempotency 키로 중복을 차단한다

예방 팁: Server Actions도 '중복/권한/검증'을 API 수준으로 설계하고, 중복 요청 시나리오를 테스트 케이스로 포함한다

참고/출처

Comments