[Svelte 시리즈 10편] 폼 액션과 검증 패턴 정립하기

English version

애니메이션으로 리듬을 잡았다면 이제 가장 중요한 입력 흐름을 단단히 만들어야 합니다.

이번 글에서 새로 나오는 용어

  1. 폼 액션(form action): SvelteKit이 폼 제출을 받을 때 실행하는 서버 함수로, HTML 기본 동작을 유지한 채 검증과 저장을 처리합니다.
  2. 프로그레시브 인핸스먼트: 자바스크립트가 없어도 기본 제출이 동작하도록 하고 가능할 때만 추가 UX를 얹는 접근 방법입니다.
  3. use
    : 폼 제출을 가로채 로딩 상태나 알림을 붙이면서도 기본 액션 흐름을 그대로 유지하게 해 주는 Svelte 지시자입니다.
  4. fail: 서버 액션에서 검증 오류를 반환할 때 쓰는 함수로, 상태 코드와 에러 메시지를 함께 돌려줍니다.

핵심 개념

폼 액션은 SvelteKit이 폼 제출을 처리하는 서버 함수입니다. HTML의 기본 제출 방식을 유지하면서 서버 검증과 상태 업데이트를 쉽게 붙일 수 있습니다. 프로그레시브 인핸스먼트(progressive enhancement)는 기본 기능이 먼저 동작하고 가능할 때만 추가 UX를 더하는 접근입니다. use:enhance는 이 철학을 살리면서 새로고침 없는 제출을 가능하게 합니다. 검증은 입력값이 조건을 만족하는지 확인하는 단계로 서버와 클라이언트 모두에서 수행해야 안전합니다.

서버와 클라이언트 역할 나눠보기

  1. 사용자가 브라우저에서 폼을 제출하면 HTML 기본 규칙이 먼저 실행됩니다. 이때 use:enhance가 있다면 새로고침을 잠깐 붙잡고 추가 UX를 끼워 넣습니다.
  2. 제출된 데이터는 자동으로 서버의 +page.server.ts 액션에 도착합니다. 여기서만 DB 접근이나 비밀 키를 읽을 수 있습니다.
  3. 액션이 return { success: true }를 하면 클라이언트 form prop으로 값이 전달되고, fail(400, ...)을 하면 같은 자리로 에러가 되돌아옵니다.
  4. 클라이언트는 서버 응답을 받기 전까지 pending 상태를 표시할 수 있고, 실패 응답이 오면 그대로 문구를 보여 줍니다.

이 순서를 천천히 따라가면 “어떤 코드가 서버인지, 어떤 코드가 브라우저인지”가 분명해집니다.

코드로 따라하기

단일 액션과 use:enhance 흐름을 함께 살펴봅니다.

<!-- src/routes/tasks/+page.svelte -->
<script>
  import { enhance } from '$app/forms';
  export let form;
  let pending = false;
</script>

<form method="POST" use:enhance={({ pending: p }) => (pending = p)}>
  <input name="title" placeholder="할 일을 입력" required />
  <button disabled={pending}>{pending ? '저장 중' : '추가'}</button>
  {#if form?.errors?.title}
    <p class="error" aria-live="polite">{form.errors.title}</p>
  {/if}
</form>
// src/routes/tasks/+page.server.ts

export const actions = {
  default: async ({ request }) => {
    const data = await request.formData();
    const title = data.get('title')?.toString().trim();
    if (!title) {
      return fail(400, { errors: { title: '제목을 입력하세요' } });
    }
    await db.task.create({ title });
    return { success: true };
  }
};

폼이 제출되면 서버 액션이 실행됩니다. fail로 반환한 객체는 자동으로 form 속성에 채워져 에러 메시지를 다시 보여 줍니다. 동시에 use:enhance가 로딩 상태를 다뤄 페이지 새로고침 없이 경험을 완성합니다.

기대 화면 확인

폼 액션은 코드보다도 "제출 전/중/후에 화면이 어떻게 보이는지"를 먼저 확인하는 것이 좋습니다.

http://localhost:5173/tasks
LIVE FORM

UI Preview

입력 흐름이 눈에 보이게 움직여야 함

폼 액션은 단순히 서버에 보내는 코드가 아니라, 사용자가 지금 어떤 상태인지 알 수 있게 만드는 UX까지 포함합니다.

use:enhancefail(400)pending

Idle

입력 전

버튼이 활성화되고 에러 메시지가 없음.

Submitting

전송 중

버튼이 비활성화되고 '저장 중' 문구 표시.

Error

검증 실패

같은 자리에서 에러 문구가 바로 보임.

Success

저장 완료

입력창이 초기화되거나 성공 메시지가 표시됨.

  • 확인할 점: 제출 중에는 중복 클릭이 막히는지
  • 확인할 점: 실패 메시지가 같은 자리에서 바로 보이는지
  • 확인할 점: 성공 후 사용자가 다음 행동을 자연스럽게 이어갈 수 있는지

왜 중요한가

공모전 신청, 동아리 보고서, 출결 수정 등 학교 프로젝트는 폼 오류가 나면 곧바로 민원이 발생합니다. 폼 액션을 사용하면 서버 검증 결과가 곧바로 화면에 반영되므로 실수가 줄어듭니다. 또한 프로그레시브 인핸스먼트 구조는 자바스크립트가 잠시 실패해도 기본 제출이 동작하므로 안정성이 높습니다.

실습

  • 따라 하기: /tasks 경로에 단일 default 액션을 만들고 빈 값 제출 시 fail 응답을 확인한다.
  • 확장하기: ?/toggle이나 ?/remove 액션을 추가해 여러 동작을 같은 페이지에서 처리한다.
  • 디버깅: form 값이 undefined이면 export let form; 선언과 서버 파일 위치를 다시 확인한다.
  • 완료 기준: use:enhance가 로딩 상태를 표시하고 성공/실패 메시지가 새로고침 없이 등장한다.

마무리

폼 액션과 검증 패턴을 정리해 두면 입력 흐름이 예상대로 움직입니다. 다음 편에서는 API 통합과 낙관적 UI를 연결해 이 폼이 실제 서버와 상호작용할 때의 경험을 살펴보겠습니다.

💬 댓글

이 글에 대한 의견을 남겨주세요