본문으로 바로가기

8단계

8단계 — 폼·검증·UX 마무리

7회 조회

목차

화면을 예쁘게 그리는 데서 한 발 더 — 사용자가 실수해도 친절하게 알려주는 게 진짜 프론트엔드입니다.

zod 로 입력값 검증

zod 는 입력 모양을 스키마 로 정의하는 라이브러리예요.

import { z } from "zod";

const SignupSchema = z.object({
  email: z.string().email("이메일 형식이 아니에요"),
  password: z.string().min(8, "비밀번호는 8자 이상"),
  age: z.number().int().min(14, "14세 이상만 가입 가능"),
});

const result = SignupSchema.safeParse(formData);
if (!result.success) {
  // result.error.issues 에 어떤 필드가 왜 틀렸는지 들어 있음
}

이 한 패턴으로 모든 폼 의 검증이 통일됩니다.

로딩 UX — 사용자 시야에 머무는 0.4 초

서버 응답을 기다리는 사이 화면이 멈춰 있으면 사용자는 고장났다 고 느껴요. 세 가지가 큰 차이를 만듭니다.

  1. 버튼 비활성화 + 텍스트 변경<button disabled>{loading ? "보내는 중…" : "보내기"}</button>
  2. 스켈레톤 UI — 콘텐츠 자리에 회색 박스 (animate-pulse)
  3. 낙관적 업데이트 — 응답 전이라도 화면을 미리 갱신, 실패 시 되돌림
const [loading, setLoading] = useState(false);

async function onSubmit() {
  setLoading(true);
  try {
    await fetch("/api/signup", { method: "POST", body: ... });
  } finally {
    setLoading(false);
  }
}

접근성 (a11y) 한 줄

<input> 옆엔 항상 <label> 을, <button> 텍스트는 행동(submit / cancel) 이 드러나게.

<label for="email">이메일</label>
<input id="email" type="email" required>

스크린리더 사용자도 어떤 항목을 채우는지 알아야 해요.

직접 해 보기

5~7단계의 Counter 를 증가/감소/리셋 세 버튼으로 확장해 보세요. 각 버튼에 aria-label 을 붙이고, 0 일 때 감소 버튼은 disabled 로.

더 깊이

다음 단계

여기까지 폼으로 텍스트 를 받았어요. 마지막 9단계 — 이미지 업로드와 최적화 에서는 사용자가 올리는 이미지 를 받습니다 — 검증·sharp 변환·사이즈 프리셋까지.