애니메이션으로 리듬을 잡았다면 이제 가장 중요한 입력 흐름을 단단히 만들어야 합니다.
이번 글에서 새로 나오는 용어
- 폼 액션(form action): SvelteKit이 폼 제출을 받을 때 실행하는 서버 함수로, HTML 기본 동작을 유지한 채 검증과 저장을 처리합니다.
- 프로그레시브 인핸스먼트: 자바스크립트가 없어도 기본 제출이 동작하도록 하고 가능할 때만 추가 UX를 얹는 접근 방법입니다.
- use: 폼 제출을 가로채 로딩 상태나 알림을 붙이면서도 기본 액션 흐름을 그대로 유지하게 해 주는 Svelte 지시자입니다.
- fail: 서버 액션에서 검증 오류를 반환할 때 쓰는 함수로, 상태 코드와 에러 메시지를 함께 돌려줍니다.
핵심 개념
폼 액션은 SvelteKit이 폼 제출을 처리하는 서버 함수입니다. HTML의 기본 제출 방식을 유지하면서 서버 검증과 상태 업데이트를 쉽게 붙일 수 있습니다. 프로그레시브 인핸스먼트(progressive enhancement)는 기본 기능이 먼저 동작하고 가능할 때만 추가 UX를 더하는 접근입니다. use:enhance는 이 철학을 살리면서 새로고침 없는 제출을 가능하게 합니다. 검증은 입력값이 조건을 만족하는지 확인하는 단계로 서버와 클라이언트 모두에서 수행해야 안전합니다.
서버와 클라이언트 역할 나눠보기
- 사용자가 브라우저에서 폼을 제출하면 HTML 기본 규칙이 먼저 실행됩니다. 이때
use:enhance가 있다면 새로고침을 잠깐 붙잡고 추가 UX를 끼워 넣습니다. - 제출된 데이터는 자동으로 서버의
+page.server.ts액션에 도착합니다. 여기서만 DB 접근이나 비밀 키를 읽을 수 있습니다. - 액션이
return { success: true }를 하면 클라이언트formprop으로 값이 전달되고,fail(400, ...)을 하면 같은 자리로 에러가 되돌아옵니다. - 클라이언트는 서버 응답을 받기 전까지
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가 로딩 상태를 다뤄 페이지 새로고침 없이 경험을 완성합니다.
기대 화면 확인
폼 액션은 코드보다도 "제출 전/중/후에 화면이 어떻게 보이는지"를 먼저 확인하는 것이 좋습니다.
💬 댓글
이 글에 대한 의견을 남겨주세요