---
type: knowledge
domain: frontend
status: active
last-reviewed: 2026-07-27
---

# 폼·입력 검증 실전

> 한 줄 정의
> 클라이언트 검증은 UX이고 **서버 검증만이 진실이다** — 폼 상태의 격리 원칙은 [[프론트엔드 원리]], 여기는 검증 시점·에러 문구·제출 상태·서버 에러 매핑의 실전 규율이다.

## 검증 이중화 — 역할이 다르므로 둘 다 필수

| 계층 | 역할 | 없으면 |
|------|------|--------|
| 클라이언트 검증 | 빠른 피드백 (제출 전에 고치게) | 제출→실패→재입력 왕복으로 UX 붕괴 |
| 서버 검증 | 데이터 무결성·보안의 유일한 방어선 | devtools로 클라 검증을 우회하면 그대로 뚫린다 → [[보안 원리]] |

- 규칙을 한 곳(스키마)에 선언하고 양쪽에서 재사용할 수 있으면 최선 — 안 되면 최소한 **규칙 명세를 API 계약 문서에 명시**해 두 구현이 어긋나는 것을 막는다.
- 클라이언트가 통과시킨 값도 서버는 다시 검증한다. "프론트에서 걸렀으니까"는 검증이 아니라 희망이다.

## 검증 시점 — 필드는 blur, 폼은 submit

| 시점 | 용도 | 함정 |
|------|------|------|
| 입력 중 (keystroke) | 형식 안내(비밀번호 강도 등) 극소수만 | 타이핑 중 빨간 에러는 "아직 다 안 썼는데 혼내는" 경험 |
| blur (필드 이탈) | 필드 단위 검증의 기본값 | 첫 진입 후 빈 채로 이탈 시 즉시 에러는 과함 — 한 번 값이 있었던 필드부터 |
| submit | 전체 검증 + 첫 에러 필드로 포커스 이동 | 에러가 화면 밖에 있으면 사용자는 "버튼이 안 눌린다"고 인식한다 |

- 한 번 에러가 표시된 필드는 **재입력 즉시 재검증**해 "고쳤는데 아직 빨갛다"를 없앤다.

## 에러 메시지 규율

- 위치는 **필드 바로 옆(아래)** — 화면 상단 요약만 있는 폼은 긴 페이지에서 원인을 못 찾는다. 요약을 쓰려면 필드별 표시와 병행하고 각 항목을 해당 필드로 앵커.
- 문구는 행동 지시형: "잘못된 입력입니다" 대신 "8자 이상, 숫자를 포함해 주세요". 무엇이 왜 안 되고 무엇을 하면 되는지 → 문구 원칙은 [[UX 라이팅 원리]].
- 에러는 색으로만 전달하지 않는다 — 텍스트+아이콘 병행, 입력과 에러를 프로그램적으로 연결(`aria-describedby`) → [[접근성 실전]].

## 제출 상태 — 중복 제출은 프론트 최다 사고

- 제출 시작 즉시 버튼을 **잠그고(pending 상태)** 진행 표시. 응답(성공·실패) 후에만 해제. 더블클릭 두 건 등록은 검증이 아니라 제출 상태 관리의 실패다.
- 단, 프론트 잠금은 UX일 뿐 — 중복 방지의 진실은 서버 멱등 키다 ([[백엔드 원리]]).
- **비활성화 함정**: 검증 통과 전 제출 버튼을 disabled로 두면 "왜 안 눌리는지"를 알 수 없다. 버튼은 활성 유지 + 누르면 에러를 보여주는 쪽이 기본값. disabled는 pending 중에만.
- 실패 시 **입력 값은 보존**한다 — 에러 한 번에 폼이 비워지는 것은 이탈 사유다.

## 파일 업로드 검증

| 검사 | 클라이언트 | 서버 |
|------|-----------|------|
| 확장자·MIME | 선택 즉시 안내 (UX) | MIME 스니핑 포함 재검증 — 확장자는 위조된다 |
| 크기 상한 | 업로드 시작 전 차단 (대역폭 절약) | 요청 크기 제한 (필수) |
| 내용 (이미지 치수·악성) | 미리보기 수준 | 진짜 검사는 서버 |

- 업로드는 진행률·취소·실패 후 재시도를 함께 설계한다. 폐쇄망(발주사 환경)은 파일 크기·확장자 정책이 보안 규정으로 고정돼 있는 경우가 많다 — 착수 전 확인 항목이다.

## 서버 에러의 필드 매핑

- API 검증 에러 응답에 **필드 식별자**가 있어야 프론트가 해당 입력 옆에 뿌릴 수 있다 — `{ errors: [{ field, code, message }] }` 류의 규약을 백엔드와 착수 초기에 합의한다(합의 없이는 전부 "알 수 없는 오류" 토스트가 된다).
- 매핑 실패분(모르는 field, 폼 전체 에러)은 폼 상단 공통 영역으로 — 삼키지 않는다.
- 서버 메시지를 그대로 노출하지 않는다 — 내부 용어·스택이 사용자에게 새는 통로다 → [[프론트엔드 에러 처리와 관측]].

## 안티패턴

- 정규식 한 방으로 이메일·전화번호를 "완벽 검증" — 오탐으로 실사용자를 막는다. 형식은 느슨하게, 진위는 서버·인증 절차로.
- placeholder를 라벨 겸 규칙 안내로 사용 — 입력 시작하면 사라진다 ([[접근성 실전]]).
- 제출 실패를 콘솔에만 남기고 화면은 침묵 — 무성 실패 금지.

## 관련 문서

- [[프론트엔드 원리]] — 폼 상태 격리·상태 4분류
- [[프론트엔드 에러 처리와 관측]] — 실패 문구·전역 에러 처리
- [[접근성 실전]] — 라벨·에러 연결·포커스
- [[UX 라이팅 원리]] — 에러 문구 작법
- [[보안 원리]] — 서버 검증=방어선, 업로드 취급
- [[00_프론트엔드 허브]]
