ID du formulaire

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.

Mise en place

  1. Placez l'id du formulaire dans la runtime config

    nuxt.config.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.

  2. Ajoutez le composant

    app/components/ContactForm.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.
  3. Utilisez-le dans une page

    app/pages/contact.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 devlocalhost. Correspond à http://localhost:3000 : Ikiform compare uniquement le nom d'hôte, jamais le port.
Votre domaineexample.com, qui couvre aussi www.example.com
Vercelyour-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.
Netlifyyour-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 Pagesyour-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 voyezPourquoiSolution
FetchError: [POST] "https://ikiform.com/f/...": 422ignoreResponseError est absent, donc $fetch lève une exception sur les erreursAjoutez ignoreResponseError: true.
404 not_found, et l'action se termine par /f/undefinedL'id du formulaire n'était pas défini au moment du buildDé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 passeLa page est rendue côté client avec ssr: falseActivez le rendu serveur ou prérendez la page.
Les soumissions arrivent dans le filtre spam avec origin_mismatchLa page qui a envoyé ne fait pas partie des domaines autorisés du formulaireAjoutez l'hôte du tableau ci-dessus. Avec Reject, les mêmes envois reçoivent plutôt 403 origin_not_allowed.