# Nuxt

> Komponen Nuxt yang mengirim sebagai formulir biasa sebelum hidrasi dan dengan $fetch setelahnya, dengan ID formulir di runtime config. Berfungsi dengan nuxt generate.

Nuxt merender formulir di server, jadi HTML yang sampai ke browser sudah berupa formulir yang berfungsi. Sampai aplikasi terhidrasi, formulir mengirim seperti formulir HTML biasa; setelah itu, komponen mengambil alih dan menampilkan hasilnya di tempat. Tidak ada yang berjalan di server Anda, jadi ini juga berfungsi sebagai situs statis yang dibangun dengan `nuxt generate`.

Untuk mengirim dari server route Nuxt dengan secret key, lihat [Vue](https://ikiform.com/id/docs/guides/vue.md#nuxt).

## Penyiapan

### Langkah 1: Taruh ID formulir di runtime config

`nuxt.config.ts`:

```ts
export default defineNuxtConfig({
  runtimeConfig: {
    public: {
      ikiformFormId: "frm_xxxxxxxxxxxxxxxx",
    },
  },
});
```

Timpa per environment dengan `NUXT_PUBLIC_IKIFORM_FORM_ID`. Dengan `nuxt generate`, nilainya ditanam saat build, jadi atur variabelnya di environment build.

### Langkah 2: Tambahkan komponen

`app/components/ContactForm.vue`:

```vue
<script setup lang="ts">
type Result =
  | { ok: true; id: string }
  | { ok: false; error: { message: string; fields?: Record<string, string[]> } };

const config = useRuntimeConfig();
const endpoint = `https://ikiform.com/f/${config.public.ikiformFormId}`;

const status = ref<"idle" | "sending" | "sent" | "error">("idle");
const errors = ref<Record<string, string[]>>({});
const message = ref("");

async function onSubmit(event: Event) {
  status.value = "sending";
  const result = await $fetch<Result>(endpoint, {
    method: "POST",
    headers: { Accept: "application/json" },
    body: new FormData(event.currentTarget as HTMLFormElement),
    ignoreResponseError: true,
  }).catch(() => null);
  if (result?.ok) {
    status.value = "sent";
    return;
  }
  errors.value = result?.error.fields ?? {};
  message.value = result?.error.message ?? "Couldn't reach the server. Try again.";
  status.value = "error";
}
</script>

<template>
  <p v-if="status === 'sent'">Thanks, we got it.</p>
  <form v-else :action="endpoint" method="post" @submit.prevent="onSubmit">
    <label>
      Email
      <input name="email" type="email" autocomplete="email" required />
    </label>
    <p v-if="errors.email">{{ errors.email[0] }}</p>
    <label>
      Message
      <textarea name="message" rows="5" />
    </label>
    <input type="hidden" name="_redirect" value="https://example.com/thanks" />
    <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
    <p v-if="status === 'error' && !errors.email" role="alert">{{ message }}</p>
    <button :disabled="status === 'sending'">{{ status === "sending" ? "Sending" : "Send" }}</button>
  </form>
</template>
```

- Di Nuxt 3, taruh di `components/`, bukan `app/components/`.
- `ignoreResponseError: true` mencegah `$fetch` melempar error pada `4xx`, jadi envelope error kembali sebagai data. Hanya kegagalan jaringan yang sampai ke `catch`.
- `@submit.prevent` baru ada setelah aplikasi terhidrasi. Sebelum itu, browser mengirim ke `action` dan mengikuti `_redirect`.

### Langkah 3: Pakai di halaman

`app/pages/contact.vue`:

```vue
<template>
  <ContactForm />
</template>
```

Fallback-nya butuh server rendering atau prerendering. Dengan `ssr: false`, halaman kosong sampai JavaScript berjalan, jadi tidak ada formulir yang bisa dikirim tanpanya.

## Domain yang diizinkan

Cantumkan domain produksi plus host pratinjau dari tempat Anda deploy.

| Tempat formulir berjalan | Tambahkan ke Domain yang diizinkan |
| --- | --- |
| `nuxt dev` | `localhost`. Ini cocok dengan `http://localhost:3000`: Ikiform hanya membandingkan nama host, tidak pernah port. |
| Domain Anda | `example.com`, yang juga mencakup `www.example.com` |
| Vercel | `your-project.vercel.app`. URL pratinjau seperti `your-project-git-branch-team.vercel.app` tidak cocok dengannya; tambahkan host pratinjau persisnya selama Anda menguji. |
| Netlify | `your-site.netlify.app`. Deploy preview (`deploy-preview-12--your-site.netlify.app`) dan branch deploy (`branch--your-site.netlify.app`) bukan subdomainnya, jadi tambahkan host pratinjau persisnya selama Anda menguji. |
| Cloudflare Pages | `your-project.pages.dev`. URL pratinjau seperti `abc123.your-project.pages.dev` adalah subdomainnya, jadi sudah cocok. |

- Jangan pernah menambahkan `vercel.app`, `netlify.app`, atau `pages.dev` saja. Setiap situs di host itu akan cocok, termasuk milik orang lain.

## Daftar periksa pengujian

1. Jalankan `nuxt dev` dengan `localhost` di domain yang diizinkan, lalu kirim. Pesan terima kasih seharusnya menggantikan formulir.
2. Matikan JavaScript di dev tools browser, muat ulang, lalu kirim. Anda seharusnya mendarat di halaman terima kasih Ikiform secara lokal, atau `/thanks` di domain `https:` Anda.
3. Kirim email yang tidak valid dengan field yang sudah didefinisikan dan pastikan pesannya muncul di bawah field.
4. Jalankan `nuxt generate`, deploy output-nya, lalu kirim dari host pratinjau.

## Pemecahan masalah

| Yang Anda lihat | Alasan | Solusi |
| --- | --- | --- |
| `FetchError: [POST] "https://ikiform.com/f/...": 422` | `ignoreResponseError` tidak ada, jadi `$fetch` melempar error | Tambahkan `ignoreResponseError: true`. |
| `404 not_found`, dan action berakhir di `/f/undefined` | ID formulir tidak diatur saat situs di-build | Atur `NUXT_PUBLIC_IKIFORM_FORM_ID` di environment build, atau nilai default di `nuxt.config.ts`. |
| Tanpa JavaScript, tidak terjadi apa-apa | Halaman dirender di client dengan `ssr: false` | Aktifkan server rendering atau prerender halamannya. |
| Kiriman masuk ke filter spam dengan `origin_mismatch` | Halaman yang mengirim tidak ada di domain yang diizinkan formulir | Tambahkan host dari tabel di atas. Jika **Reject** dipilih, kiriman yang sama mendapat `403 origin_not_allowed`. |

## Terkait

- [Vue](https://ikiform.com/id/docs/guides/vue.md): Single-file component.
- [JavaScript biasa](https://ikiform.com/id/docs/guides/javascript.md): `fetch` dengan error inline.
- [Domain yang diizinkan](https://ikiform.com/id/docs/spam.md#allowed-domains): Cara origin dicocokkan, dan apa akibat ketidakcocokan.
- [Error](https://ikiform.com/id/docs/errors.md): Setiap kode error, beserta penyebab dan solusinya.
