← 전체 글로 돌아가기

Next.js

Next.js 서버 액션에서 FormData 값이 비어 보였던 이유

서버 액션으로 폼을 옮긴 뒤 값이 비어 보일 때 내가 실제로 확인한 순서와 수정 기준을 정리했다.

증상은 단순했는데 원인은 폼 구조였다

관리자 화면의 작은 설정 폼을 API 라우트에서 서버 액션으로 옮긴 적이 있다. 입력창은 보이고 제출도 되는데 서버에서 formData.get("title")을 찍으면 null이 나왔다. 처음에는 Next.js 캐시나 서버 액션 버전 문제라고 생각했는데, 실제 원인은 훨씬 가까운 곳에 있었다.

버튼을 컴포넌트로 분리하면서 form 태그 밖으로 빠졌고, 입력 필드 일부에는 name이 빠져 있었다. 화면에서는 상태값으로 잘 보이니 더 헷갈렸다.

먼저 확인한 것

나는 서버 쪽에서 바로 저장하지 않고, 받은 키를 먼저 출력했다.

'use server'

export async function savePost(formData: FormData) {
  console.log(Array.from(formData.keys()))
  console.log(formData.get('title'))
}

브라우저에서도 실제 제출되는 폼 범위를 확인했다.

# 개발 서버 로그를 보면서 제출
pnpm dev

여기서 키 목록이 비어 있으면 데이터베이스나 검증 라이브러리로 내려가기 전에 HTML 구조부터 봐야 한다.

내가 고친 기준

폼을 고칠 때는 아래 기준으로 정리했다.

  • 서버에서 읽을 값은 반드시 name을 붙인다.
  • 제출 버튼은 form 내부에 두거나 form 속성으로 연결한다.
  • React 상태값만 믿지 말고 실제 FormData 키를 확인한다.
  • 저장 함수 안에서는 null을 문자열로 바로 변환하지 않는다.

예를 들어 버튼이 바깥에 있어야 하는 레이아웃이라면 이렇게 연결했다.

<form id="post-form" action={savePost}>
  <input name="title" defaultValue="" />
</form>

<button type="submit" form="post-form">
  저장
</button>

헷갈렸던 지점

valueonChange가 붙어 있으면 제출 데이터도 당연히 따라갈 거라고 생각했다. 하지만 서버 액션은 최종적으로 HTML 폼 제출 결과를 받는다. 상태가 예쁘게 바뀌는 것과 FormData에 들어가는 것은 다른 문제였다.

다음부터 남겨 둘 체크

  • 새 폼을 만들면 Array.from(formData.entries())를 한 번 찍는다.
  • 필수 필드는 서버에서 typeof value === "string"까지 확인한다.
  • 컴포넌트 분리 후 버튼 위치가 바뀌면 제출 범위를 다시 본다.

이 문제는 큰 버그처럼 보였지만 수정은 작았다. 그래서 더더욱 저장 로직보다 폼의 기본 규칙을 먼저 확인하는 습관이 필요했다.