# SvelteKit

> 폼이 Ikiform으로 바로 POST하고, JavaScript가 로드되면 fetch로 향상되는 프리렌더링 SvelteKit 페이지. adapter-static과 함께 작동합니다.

이 페이지는 서버가 필요 없습니다. 폼의 `action`이 Ikiform 엔드포인트이므로 `adapter-static`과 모든 정적 호스트에서 작동합니다. JavaScript가 없으면 일반 HTML처럼 POST하고 리디렉션하며, JavaScript가 있으면 `fetch`로 POST하고 결과를 그 자리에 보여 줍니다. 대신 시크릿 키를 사용하는 폼 액션으로 POST하려면 [Svelte](https://ikiform.com/ko/docs/guides/svelte.md#sveltekit)를 참고하세요.

## use:enhance를 쓰지 않는 이유

SvelteKit의 `use:enhance`는 `+page.server.js` 파일에 정의된 액션을 가리키는 `method="POST"` 폼에서만 작동합니다. Ikiform은 외부 URL이므로 이 페이지는 대신 작은 `onsubmit` 핸들러를 사용합니다. 점진적 향상 방식은 같습니다. 폼이 먼저 작동하고, 스크립트가 이를 개선합니다.

## 설정하기

### 1단계: 공개 환경 변수 추가하기

`.env`:

```bash
PUBLIC_IKIFORM_FORM_ID=frm_xxxxxxxxxxxxxxxx
PUBLIC_SITE_URL=https://example.com
```

`PUBLIC_SITE_URL`은 JavaScript 없는 경우의 리디렉션에 사용합니다. 프리렌더링 중에는 `page.url.origin`이 여러분의 도메인이 아니라 `http://sveltekit-prerender`이므로, 이 값으로 리디렉션 주소를 만들 수 없습니다.

### 2단계: 페이지 프리렌더링하기

`src/routes/contact/+page.js`:

```js
export const prerender = true;
```

### 3단계: 폼 추가하기

`src/routes/contact/+page.svelte`:

```svelte
<script>
  import { PUBLIC_IKIFORM_FORM_ID, PUBLIC_SITE_URL } from "$env/static/public";

  const endpoint = `https://ikiform.com/f/${PUBLIC_IKIFORM_FORM_ID}`;

  let status = $state("idle");
  let errors = $state({});
  let message = $state("");

  async function onsubmit(event) {
    event.preventDefault();
    status = "sending";
    try {
      const response = await fetch(endpoint, {
        method: "POST",
        headers: { Accept: "application/json" },
        body: new FormData(event.currentTarget),
      });
      const result = await response.json();
      if (result.ok) {
        status = "sent";
        return;
      }
      errors = result.error.fields ?? {};
      message = result.error.message;
    } catch {
      message = "Couldn't reach the server. Try again.";
    }
    status = "error";
  }
</script>

{#if status === "sent"}
  <p>Thanks, we got it.</p>
{:else}
  <form action={endpoint} method="POST" {onsubmit}>
    <label>
      Email
      <input name="email" type="email" autocomplete="email" required aria-invalid={errors.email ? "true" : undefined} />
    </label>
    {#if errors.email}<p>{errors.email[0]}</p>{/if}
    <label>
      Message
      <textarea name="message" rows="5"></textarea>
    </label>
    <input type="hidden" name="_redirect" value={`${PUBLIC_SITE_URL}/contact/thanks`} />
    <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
    {#if status === "error" && !errors.email}<p role="alert">{message}</p>{/if}
    <button disabled={status === "sending"}>{status === "sending" ? "Sending" : "Send"}</button>
  </form>
{/if}
```

리디렉션 대상으로 `src/routes/contact/thanks/+page.svelte`를 추가하세요.

## SvelteKit의 CSP를 사용한다면

`kit.csp`를 설정했다면 `fetch` POST와 일반 폼 POST 모두에 Ikiform을 허용하세요. 그렇지 않으면 브라우저가 둘 중 하나를 막습니다.

`svelte.config.js`:

```js
const config = {
  kit: {
    csp: {
      directives: {
        "connect-src": ["self", "https://ikiform.com"],
        "form-action": ["self", "https://ikiform.com"],
      },
    },
  },
};

export default config;
```

## Allowed domains

프로덕션과 미리보기 호스트를 등록하세요.

| 폼이 실행되는 곳 | Allowed domains에 추가할 값 |
| --- | --- |
| `vite dev` | `localhost`. `http://localhost:5173`과 일치합니다. Ikiform은 포트가 아니라 호스트 이름만 비교합니다. |
| 여러분의 도메인 | `example.com`, `www.example.com`도 포함 |
| Vercel | `your-project.vercel.app`. `your-project-git-branch-team.vercel.app` 같은 미리보기 URL은 일치하지 않으므로, 테스트하는 동안 정확한 미리보기 호스트를 추가하세요. |
| Cloudflare Pages | `your-project.pages.dev`. `abc123.your-project.pages.dev` 같은 미리보기 URL은 하위 도메인이므로 이미 일치합니다. |
| Netlify | `your-site.netlify.app`. 배포 미리보기(`deploy-preview-12--your-site.netlify.app`)와 브랜치 배포(`branch--your-site.netlify.app`)는 하위 도메인이 아니므로, 테스트하는 동안 정확한 미리보기 호스트를 추가하세요. |

- `vercel.app`, `pages.dev`, `netlify.app`만 단독으로 추가하지 마세요. 다른 사람의 사이트를 포함해 그 호스트의 모든 사이트가 일치하게 됩니다.

## 테스트 체크리스트

1. 허용 도메인에 `localhost`를 넣고 개발 서버를 실행해 제출하세요. 감사 메시지가 폼을 대체해야 합니다.
2. `vite build`를 실행하고, 결과물에 폼이 이미 들어 있는 `contact.html`이 있는지 확인하세요.
3. JavaScript를 끄고 새로 고친 뒤 제출하세요. `https:` 도메인에서는 `/contact/thanks`로 이동해야 합니다.
4. 미리보기 배포에서 제출하고 스팸으로 표시되지 않는지 확인하세요.

## 문제 해결

| 증상 | 원인 | 해결 |
| --- | --- | --- |
| 빌드가 `PUBLIC_IKIFORM_FORM_ID`가 `$env/static/public`에서 export되지 않았다며 실패함 | 빌드가 실행되는 곳에 변수가 설정되지 않음 | 로컬의 `.env`와 호스트의 빌드 설정에 추가하세요. |
| 제출해도 아무 일이 없고, 콘솔에 `x-sveltekit-action` 헤더에 관한 CORS 오류가 표시됨 | 앱 밖으로 POST하는 폼에 `use:enhance`를 사용함 | 제거하고 `onsubmit` 핸들러를 유지하세요. |
| `form-action`이나 `connect-src` 때문에 브라우저가 폼 전송을 거부함 | `kit.csp`에 Ikiform이 없음 | 두 지시어 모두에 `https://ikiform.com`을 추가하세요. |
| JavaScript 없이 제출하면 Ikiform의 감사 페이지로 이동함 | `PUBLIC_SITE_URL`이 `http:`이거나, 도메인이 허용 도메인에 없음 | `https:` 주소를 사용하고 도메인을 추가하세요. |
| 제출이 `origin_mismatch`와 함께 스팸 필터에 들어감 | POST한 페이지가 폼의 허용 도메인에 없음 | 위 표의 호스트를 추가하세요. **Reject**를 골랐다면 같은 POST가 대신 `403 origin_not_allowed`를 받습니다. |

## 관련 문서

- [Svelte](https://ikiform.com/ko/docs/guides/svelte.md): Svelte 5 컴포넌트와 서버 액션.
- [Astro](https://ikiform.com/ko/docs/guides/astro.md): JavaScript 없는 폼, 원하면 향상 적용.
- [Allowed domains](https://ikiform.com/ko/docs/spam.md#allowed-domains): 출처를 비교하는 방식과 불일치 시 동작.
- [오류](https://ikiform.com/ko/docs/errors.md): 모든 오류 코드와 원인, 해결 방법.
