JavaScript가 꺼져 있어도 폼이 동작합니다 — SvelteKit Form Actions의 프로그레시브 인핸스먼트입니다.

개념 정의

Form Actions 는 SvelteKit에서 <form> 제출을 서버에서 처리하는 메커니즘입니다. +page.server.js에 actions를 정의하면, HTML의 기본 폼 제출 방식으로 동작하면서도 JavaScript가 활성화되면 향상된 경험을 제공합니다.

기본 사용법

JAVASCRIPT
// src/routes/login/+page.server.js
import { fail, redirect } from '@sveltejs/kit';

export const actions = {
  // 기본 action (method="POST"로 제출 시)
  default: async ({ request, cookies }) => {
    const data = await request.formData();
    const email = data.get('email');
    const password = data.get('password');

    // 유효성 검사
    if (!email) {
      return fail(400, { email, missing: true });
    }

    // 인증 로직
    const user = await authenticate(email, password);
    if (!user) {
      return fail(401, { email, incorrect: true });
    }

    cookies.set('session', user.sessionId, { path: '/' });
    redirect(303, '/dashboard');
  }
};
SVELTE
<!-- src/routes/login/+page.svelte -->
<script>
  import { enhance } from '$app/forms';
  let { form } = $props();
</script>

<h1>로그인</h1>

<!-- use:enhance로 프로그레시브 인핸스먼트 -->
<form method="POST" use:enhance>
  <label>
    이메일
    <input name="email" type="email" value={form?.email ?? ''} />
  </label>

  {#if form?.missing}
    <p class="error">이메일을 입력해주세요.</p>
  {/if}

  <label>
    비밀번호
    <input name="password" type="password" />
  </label>

  {#if form?.incorrect}
    <p class="error">이메일 또는 비밀번호가 올바르지 않습니다.</p>
  {/if}

  <button type="submit">로그인</button>
</form>

Named Actions

여러 액션을 구분할 때 사용합니다.

JAVASCRIPT
// src/routes/todos/+page.server.js
export const actions = {
  create: async ({ request }) => {
    const data = await request.formData();
    const text = data.get('text');
    await db.todo.create({ data: { text } });
  },

  delete: async ({ request }) => {
    const data = await request.formData();
    const id = data.get('id');
    await db.todo.delete({ where: { id } });
  },

  toggle: async ({ request }) => {
    const data = await request.formData();
    const id = data.get('id');
    const todo = await db.todo.findUnique({ where: { id } });
    await db.todo.update({
      where: { id },
      data: { done: !todo.done }
    });
  }
};
SVELTE
<!-- action 속성으로 어떤 action을 호출할지 지정 -->
<form method="POST" action="?/create" use:enhance>
  <input name="text" placeholder="할 일 입력" />
  <button>추가</button>
</form>

{#each data.todos as todo}
  <div>
    <form method="POST" action="?/toggle" use:enhance>
      <input type="hidden" name="id" value={todo.id} />
      <button>{todo.done ? '완료' : '미완료'}: {todo.text}</button>
    </form>

    <form method="POST" action="?/delete" use:enhance>
      <input type="hidden" name="id" value={todo.id} />
      <button>삭제</button>
    </form>
  </div>
{/each}

use:enhance 커스터마이징

SVELTE
<script>
  import { enhance } from '$app/forms';
  let loading = $state(false);
</script>

<form
  method="POST"
  use:enhance={() => {
    loading = true;

    return async ({ result, update }) => {
      loading = false;

      if (result.type === 'success') {
        // 성공 시 커스텀 로직
        alert('저장 완료!');
      }

      // 기본 동작 실행 (폼 초기화, 데이터 갱신 등)
      await update();
    };
  }}
>
  <input name="title" />
  <button disabled={loading}>
    {loading ? '저장 중...' : '저장'}
  </button>
</form>

파일 업로드

JAVASCRIPT
// +page.server.js
export const actions = {
  upload: async ({ request }) => {
    const data = await request.formData();
    const file = data.get('file');

    if (!(file instanceof File) || file.size === 0) {
      return fail(400, { message: '파일을 선택해주세요' });
    }

    // 파일 크기 제한 (5MB)
    if (file.size > 5 * 1024 * 1024) {
      return fail(400, { message: '파일 크기는 5MB 이하여야 합니다' });
    }

    const buffer = await file.arrayBuffer();
    // 파일 저장 로직...

    return { success: true, filename: file.name };
  }
};
SVELTE
<form method="POST" action="?/upload" enctype="multipart/form-data" use:enhance>
  <input type="file" name="file" accept="image/*" />
  <button>업로드</button>
</form>

면접 포인트

  • "프로그레시브 인핸스먼트가 뭔가요?": JavaScript 없이도 기본 HTML 폼으로 동작하고, JavaScript가 활성화되면 use:enhance가 AJAX로 업그레이드하여 전체 페이지 새로고침 없이 동작합니다.
  • "Form Actions vs API Routes?": 폼 제출 처리에는 Form Actions가 적합합니다. 프로그레시브 인핸스먼트, CSRF 보호, 자동 데이터 재로딩을 기본 제공하기 때문입니다. API Routes는 외부 클라이언트(모바일 앱 등)가 호출할 때 적합합니다.

정리

Form Actions는 "서버에서 폼을 처리하는 가장 Svelte다운 방법"입니다. HTML 표준 폼 동작을 유지하면서 use:enhance로 UX를 향상시키고, fail()로 에러를 깔끔하게 처리할 수 있습니다.

댓글 로딩 중...