# SvelteKit

> フォームを Ikiform に直接送信し、JavaScript が読み込まれると fetch で拡張される、プリレンダリングされた SvelteKit のページ。adapter-static でも動きます。

このページにサーバーは不要です。フォームの `action` は Ikiform のエンドポイントなので、`adapter-static` やどの静的ホストでも動きます。JavaScript がなければプレーンな HTML のように送信してリダイレクトし、JavaScript があれば `fetch` で送信して結果をその場に表示します。代わりにシークレットキーを使ってフォームアクション経由で送信するには、[Svelte](https://ikiform.com/ja/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` の送信とプレーンなフォームの送信の両方で 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;
```

## 許可ドメイン

本番に加えて、プレビューのホストを登録します。

| フォームが動く場所 | 許可ドメインに追加するもの |
| --- | --- |
| `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` からエクスポートされていないというエラーでビルドが失敗する | ビルドを実行する環境で変数が設定されていない | ローカルでは `.env` に、ホスティング先ではビルド設定に追加します。 |
| 送信しても何も起きず、コンソールに `x-sveltekit-action` ヘッダーに関する CORS エラーが表示される | アプリの外に送信するフォームに `use:enhance` が付いている | それを削除し、`onsubmit` ハンドラーを残します。 |
| `form-action` または `connect-src` のせいで、ブラウザーがフォームの送信を拒否する | `kit.csp` に Ikiform が含まれていない | 両方のディレクティブに `https://ikiform.com` を追加します。 |
| JavaScript なしだと Ikiform のサンクスページに移動する | `PUBLIC_SITE_URL` が `http:`、またはドメインが許可ドメインにない | `https:` のアドレスを使い、ドメインを追加します。 |
| 送信データが `origin_mismatch` でスパムフィルターの下に届く | 送信したページが、フォームの許可ドメインに含まれていない | 上の表のホストを追加します。**Reject** を選んでいる場合、同じ送信には代わりに `403 origin_not_allowed` が返ります。 |

## 関連ページ

- [Svelte](https://ikiform.com/ja/docs/guides/svelte.md): Svelte 5 のコンポーネントと Server Action。
- [Astro](https://ikiform.com/ja/docs/guides/astro.md): JavaScript なしのフォーム。必要に応じて拡張。
- [許可ドメイン](https://ikiform.com/ja/docs/spam.md#allowed-domains): オリジンの照合方法と、一致しない場合の動作。
- [エラー](https://ikiform.com/ja/docs/errors.md): すべてのエラーコードと、その原因と対処。
