Next.js Server Actions + React Hook Form 검증 기준

2026.05.09·수정 2026.07.19·약 6분

이 글에서 정리하는 내용

Server Actions와 React Hook Form을 같이 쓸 때 클라이언트 검증과 서버 검증 역할이 겹쳐 보이는 상황을 기준으로 원인을 좁히고, 실제 프로젝트에서 어떤 설정과 코드 구조를 확인해야 하는지 정리합니다. 단순 개념 소개가 아니라 오류가 난 순간 바로 확인할 순서와 선택 기준을 중심으로 설명합니다.

Server Actions와 React Hook Form의 역할 차이

Server Actions와 React Hook Form의 역할 차이 설명 이미지

Server Actions와 React Hook Form을 같이 쓸 때는 “사용자 입력 경험”과 “서버에서 신뢰할 검증”을 분리해서 봐야 합니다. 클라이언트 검증은 빠른 피드백을 주고, Server Action 검증은 실제 저장 전에 반드시 다시 확인하는 안전장치입니다.

먼저 form 제출이 일반 action으로 Server Action에 들어가는지, React Hook Form의 handleSubmit을 거쳐 호출되는지 확인합니다. 에러 메시지 표시 흐름은 React Hook Form 에러 메시지 표시 문제 해결도 함께 보면 좋습니다.

클라이언트 검증과 서버 검증을 나누는 기준

클라이언트 검증은 빈 값, 형식 오류, 즉각적인 입력 피드백에 강합니다. 하지만 브라우저에서 통과했다고 해서 서버가 그 값을 믿으면 안 됩니다. 권한, 중복 데이터, DB 제약, 최종 schema 검증은 Server Action 안에서 다시 처리해야 합니다.

Zod schema를 같이 쓴다면 클라이언트에서는 React Hook Form resolver로 사용자 피드백을 주고, 서버에서는 같은 의미의 schema로 FormData를 다시 검증합니다. 중복처럼 보여도 목적이 다릅니다. 하나는 UX이고, 하나는 신뢰 경계입니다.

// app/inquiries/form.tsx
const form = useForm<FormValues>({ resolver: zodResolver(schema) });

// app/inquiries/actions.ts
export async function createInquiry(formData: FormData) {
  "use server";
}

위 예시는 클라이언트의 useForm과 서버의 createInquiry가 서로 다른 위치에서 동작한다는 점을 보여줍니다. form 입력 경험은 클라이언트가 맡고, 실제 저장 여부는 서버가 최종 판단합니다.

실무에서는 schema를 완전히 따로 흩뜨리면 시간이 지나며 기준이 달라질 수 있습니다. 가능한 한 같은 필드 정의를 공유하거나, 최소한 클라이언트와 서버 에러 메시지가 같은 규칙을 설명하도록 맞춰야 합니다.

Zod schema를 함께 쓸 때의 구조

Zod schema를 함께 쓸 때 가장 중요한 기준은 서버 검증을 생략하지 않는 것입니다. React Hook Form resolver가 있어도 사용자는 요청을 직접 보낼 수 있고, 브라우저 검증은 쉽게 우회될 수 있습니다.

Server Action에서 검증 실패를 반환한다면 화면은 그 상태를 사용자가 이해할 수 있게 보여줘야 합니다. Next.js의 form guide처럼 useActionState를 쓰는 구조에서는 서버가 반환한 에러를 form 상태로 연결하고, React Hook Form을 함께 쓰는 구조에서는 제출 흐름이 중복되지 않게 정리합니다.

성공·실패 상태를 화면에 보여주는 흐름

성공·실패 상태를 화면에 보여주는 흐름 설명 이미지

성공·실패 상태는 제출 버튼 주변, field error, 전체 form 메시지로 나눠 보여주는 것이 좋습니다. 서버에서 실패한 값은 field별 에러로 돌려줄 수 있으면 가장 좋고, 권한이나 중복 요청처럼 field 하나에 묶기 어려운 문제는 form-level message로 보여줍니다.

성공 후에는 form reset, redirect, toast, 목록 재검증 중 어떤 흐름이 맞는지 정합니다. 저장은 됐는데 화면이 stale하게 남는다면 Server Action 자체보다 이후 갱신 흐름이 빠진 것일 수 있습니다.

간단한 폼과 복잡한 폼의 선택 기준

간단한 문의 폼이라면 Server Action과 기본 form만으로도 충분할 수 있습니다. 입력이 많고 즉각적인 field validation, dirty state, 복잡한 UI 제어가 필요하다면 React Hook Form을 함께 쓰는 편이 낫습니다.

구조를 정할 때는 “이 폼은 Server Action 기본 제출”, “이 폼은 React Hook Form으로 클라이언트 검증 후 Server Action 호출”, “서버 검증 에러는 fieldErrors로 반환”처럼 기준을 남겨둡니다.

가능하면 수정 전후를 한 줄로 기록해 두세요. 예를 들어 “클라이언트 resolver는 UX용으로 유지”, “Server Action에서 Zod safeParse 추가”, “서버 field error를 form 메시지로 연결”처럼 남기면 됩니다.

피해야 할 방식은 클라이언트 검증이 있으니 서버 검증을 생략하는 것입니다. 반대로 모든 검증을 서버에만 맡기면 사용자는 제출 후에야 단순한 입력 오류를 알게 됩니다. 두 검증은 경쟁 관계가 아니라 역할 분담입니다.

수정 후에는 빈 값, 형식 오류, 서버 중복 오류, 성공 케이스를 각각 확인합니다. 클라이언트에서 막히는 오류와 서버에서 돌아오는 오류가 화면에 같은 방식으로 이해되도록 보이는지가 핵심입니다.

이 글의 핵심은 Server Actions와 React Hook Form 중 하나만 고르는 것이 아닙니다. 빠른 입력 피드백은 클라이언트에서, 신뢰할 최종 검증과 저장은 서버에서 처리하도록 역할을 나누는 것입니다.

이 글이 마음에 드세요?

RSS 피드를 구독하세요!

댓글 남기기