# Nuxt

> Un composant Nuxt qui envoie comme un formulaire classique avant l'hydratation, puis avec $fetch, avec l'id du formulaire dans la runtime config. Compatible avec nuxt generate.

Nuxt rend le formulaire côté serveur, donc le HTML qui arrive dans le navigateur est déjà un formulaire fonctionnel. Tant que l'app n'est pas hydratée, il envoie comme n'importe quel formulaire HTML ; ensuite, le composant prend le relais et affiche le résultat sur place. Rien ne tourne sur votre serveur, donc cela fonctionne aussi en site statique généré avec `nuxt generate`.

Pour envoyer plutôt depuis une route serveur Nuxt avec une clé secrète, voir [Vue](https://ikiform.com/fr/docs/guides/vue.md#nuxt).

## Mise en place

### Étape 1 : Placez l'id du formulaire dans la runtime config

`nuxt.config.ts`:

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

Surchargez-le par environnement avec `NUXT_PUBLIC_IKIFORM_FORM_ID`. Avec `nuxt generate`, la valeur est figée au moment du build : définissez donc la variable dans l'environnement de build.

### Étape 2 : Ajoutez le composant

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

- Sur Nuxt 3, placez-le dans `components/` au lieu de `app/components/`.
- `ignoreResponseError: true` empêche `$fetch` de lever une exception sur les `4xx`, donc l'enveloppe d'erreur revient sous forme de données. Seules les pannes réseau atteignent `catch`.
- `@submit.prevent` n'existe qu'une fois l'app hydratée. Avant cela, le navigateur envoie vers `action` et suit `_redirect`.

### Étape 3 : Utilisez-le dans une page

`app/pages/contact.vue`:

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

La solution de repli nécessite le rendu serveur ou le prérendu. Avec `ssr: false`, la page reste vide tant que JavaScript ne s'exécute pas, donc il n'y a aucun formulaire à envoyer sans lui.

## Domaines autorisés

Listez la production ainsi que les hôtes de prévisualisation de votre hébergeur.

| Où tourne le formulaire | À ajouter aux domaines autorisés |
| --- | --- |
| `nuxt dev` | `localhost`. Correspond à `http://localhost:3000` : Ikiform compare uniquement le nom d'hôte, jamais le port. |
| Votre domaine | `example.com`, qui couvre aussi `www.example.com` |
| Vercel | `your-project.vercel.app`. Les URL de prévisualisation comme `your-project-git-branch-team.vercel.app` ne correspondent pas ; ajoutez l'hôte de prévisualisation exact le temps de le tester. |
| Netlify | `your-site.netlify.app`. Les deploy previews (`deploy-preview-12--your-site.netlify.app`) et les branch deploys (`branch--your-site.netlify.app`) n'en sont pas des sous-domaines : ajoutez l'hôte de prévisualisation exact le temps de le tester. |
| Cloudflare Pages | `your-project.pages.dev`. Les URL de prévisualisation comme `abc123.your-project.pages.dev` en sont des sous-domaines, donc elles correspondent déjà. |

- N'ajoutez jamais `vercel.app`, `netlify.app` ou `pages.dev` seul. Tous les sites de cet hébergeur correspondraient, y compris ceux des autres.

## Checklist de test

1. Lancez `nuxt dev` avec `localhost` dans les domaines autorisés et envoyez. Le message de remerciement doit remplacer le formulaire.
2. Désactivez JavaScript dans les outils de développement de votre navigateur, rechargez et envoyez. Vous devez arriver sur la page de remerciement d'Ikiform en local, ou sur `/thanks` sur votre domaine `https:`.
3. Envoyez un e-mail invalide avec des champs définis et vérifiez que le message apparaît sous le champ.
4. Lancez `nuxt generate`, déployez le résultat et envoyez depuis l'hôte de prévisualisation.

## Dépannage

| Ce que vous voyez | Pourquoi | Solution |
| --- | --- | --- |
| `FetchError: [POST] "https://ikiform.com/f/...": 422` | `ignoreResponseError` est absent, donc `$fetch` lève une exception sur les erreurs | Ajoutez `ignoreResponseError: true`. |
| `404 not_found`, et l'action se termine par `/f/undefined` | L'id du formulaire n'était pas défini au moment du build | Définissez `NUXT_PUBLIC_IKIFORM_FORM_ID` dans l'environnement de build, ou la valeur par défaut dans `nuxt.config.ts`. |
| Sans JavaScript, rien ne se passe | La page est rendue côté client avec `ssr: false` | Activez le rendu serveur ou prérendez la page. |
| Les soumissions arrivent dans le filtre spam avec `origin_mismatch` | La page qui a envoyé ne fait pas partie des domaines autorisés du formulaire | Ajoutez l'hôte du tableau ci-dessus. Avec **Reject**, les mêmes envois reçoivent plutôt `403 origin_not_allowed`. |

## Voir aussi

- [Vue](https://ikiform.com/fr/docs/guides/vue.md): Un composant monofichier.
- [JavaScript pur](https://ikiform.com/fr/docs/guides/javascript.md): `fetch` avec erreurs affichées en ligne.
- [Domaines autorisés](https://ikiform.com/fr/docs/spam.md#allowed-domains): Comment les origines sont comparées, et ce qui se passe en cas de non-correspondance.
- [Erreurs](https://ikiform.com/fr/docs/errors.md): Chaque code d'erreur, avec sa cause et sa solution.
