# Nuxt

> Eine Nuxt-Komponente, die vor der Hydration als einfaches Formular sendet und danach mit $fetch, mit der Formular-ID in der Runtime-Config. Funktioniert mit nuxt generate.

Nuxt rendert das Formular auf dem Server, daher ist das HTML, das im Browser ankommt, bereits ein funktionierendes Formular. Bis die App hydriert, sendet es wie jedes HTML-Formular; danach übernimmt die Komponente und zeigt das Ergebnis direkt an. Auf deinem Server läuft nichts, daher funktioniert es auch als statische Site, gebaut mit `nuxt generate`.

Um stattdessen aus einer Nuxt-Server-Route mit einem geheimen Schlüssel zu senden, siehe [Vue](https://ikiform.com/de/docs/guides/vue.md#nuxt).

## Einrichten

### Schritt 1: Lege die Formular-ID in der Runtime-Config ab

`nuxt.config.ts`:

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

Überschreibe sie pro Umgebung mit `NUXT_PUBLIC_IKIFORM_FORM_ID`. Mit `nuxt generate` wird der Wert beim Build fest eingebaut, setze die Variable also in der Build-Umgebung.

### Schritt 2: Füge die Komponente hinzu

`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>
```

- Lege sie unter Nuxt 3 in `components/` statt in `app/components/` ab.
- `ignoreResponseError: true` verhindert, dass `$fetch` bei `4xx` wirft, sodass der Fehler-Envelope als Daten zurückkommt. Nur Netzwerkfehler landen in `catch`.
- `@submit.prevent` existiert erst, wenn die App hydriert ist. Davor sendet der Browser an `action` und folgt `_redirect`.

### Schritt 3: Verwende sie auf einer Seite

`app/pages/contact.vue`:

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

Der Fallback braucht Server-Rendering oder Prerendering. Mit `ssr: false` ist die Seite leer, bis JavaScript läuft, ohne JavaScript gibt es also kein Formular zum Absenden.

## Erlaubte Domains

Liste die Produktion plus die Preview-Hosts deines Deployment-Ziels auf.

| Wo das Formular läuft | Zu Allowed domains hinzufügen |
| --- | --- |
| `nuxt dev` | `localhost`. Passt auf `http://localhost:3000`: Ikiform vergleicht nur den Hostnamen, nie den Port. |
| Deine Domain | `example.com`, was auch `www.example.com` abdeckt |
| Vercel | `your-project.vercel.app`. Preview-URLs wie `your-project-git-branch-team.vercel.app` passen nicht darauf; füge den exakten Preview-Host hinzu, während du eine Preview testest. |
| Netlify | `your-site.netlify.app`. Deploy Previews (`deploy-preview-12--your-site.netlify.app`) und Branch Deploys (`branch--your-site.netlify.app`) sind keine Subdomains davon, füge also den exakten Preview-Host hinzu, während du eine testest. |
| Cloudflare Pages | `your-project.pages.dev`. Preview-URLs wie `abc123.your-project.pages.dev` sind Subdomains davon und passen daher bereits. |

- Füge niemals `vercel.app`, `netlify.app` oder `pages.dev` allein hinzu. Jede Site auf diesem Host würde passen, auch die von anderen Leuten.

## Test-Checkliste

1. Starte `nuxt dev` mit `localhost` in den erlaubten Domains und sende ab. Die Dankesnachricht sollte das Formular ersetzen.
2. Schalte JavaScript in den Dev-Tools deines Browsers aus, lade neu und sende ab. Lokal solltest du auf der Dankesseite von Ikiform landen, auf deiner `https:`-Domain auf `/thanks`.
3. Sende bei definierten Feldern eine ungültige E-Mail ab und prüfe, dass die Meldung unter dem Feld erscheint.
4. Starte `nuxt generate`, deploye die Ausgabe und sende vom Preview-Host ab.

## Fehlerbehebung

| Was du siehst | Warum | Lösung |
| --- | --- | --- |
| `FetchError: [POST] "https://ikiform.com/f/...": 422` | `ignoreResponseError` fehlt, daher wirft `$fetch` bei Fehlern | Füge `ignoreResponseError: true` hinzu. |
| `404 not_found`, und die action endet auf `/f/undefined` | Die Formular-ID war beim Build der Site nicht gesetzt | Setze `NUXT_PUBLIC_IKIFORM_FORM_ID` in der Build-Umgebung oder den Standardwert in `nuxt.config.ts`. |
| Ohne JavaScript passiert nichts | Die Seite wird mit `ssr: false` clientseitig gerendert | Aktiviere Server-Rendering oder prerendere die Seite. |
| Einsendungen landen im Spamfilter mit `origin_mismatch` | Die sendende Seite steht nicht in den erlaubten Domains des Formulars | Füge den Host aus der Tabelle oben hinzu. Mit **Reject** bekommen dieselben Requests stattdessen `403 origin_not_allowed`. |

## Verwandte Themen

- [Vue](https://ikiform.com/de/docs/guides/vue.md): Eine Single-File-Komponente.
- [Reines JavaScript](https://ikiform.com/de/docs/guides/javascript.md): `fetch` mit Inline-Fehlern.
- [Erlaubte Domains](https://ikiform.com/de/docs/spam.md#allowed-domains): Wie Origins abgeglichen werden und was eine Abweichung bewirkt.
- [Fehler](https://ikiform.com/de/docs/errors.md): Jeder Fehlercode mit Ursache und Lösung.
