ID del formulario

Nuxt

Un componente de Nuxt que envía como un formulario normal antes de la hidratación y con $fetch después, con el id del formulario en la runtime config. Funciona con nuxt generate.

Nuxt renderiza el formulario en el servidor, así que el HTML que llega al navegador ya es un formulario que funciona. Hasta que la app se hidrata, envía como cualquier formulario HTML; después, el componente toma el control y muestra el resultado en su sitio. No se ejecuta nada en tu servidor, así que también funciona como sitio estático generado con nuxt generate.

Para enviar desde una ruta de servidor de Nuxt con una clave secreta, consulta Vue.

Configúralo

  1. Pon el id del formulario en la runtime config

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

    Sobrescríbelo por entorno con NUXT_PUBLIC_IKIFORM_FORM_ID. Con nuxt generate, el valor se fija en tiempo de build, así que define la variable en el entorno de build.

  2. Añade el componente

    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>
    • En Nuxt 3, ponlo en components/ en lugar de app/components/.
    • ignoreResponseError: true evita que $fetch lance una excepción ante un 4xx, así que el formato de error vuelve como datos. Solo los fallos de red llegan al catch.
    • @submit.prevent solo existe cuando la app ya se hidrató. Antes de eso, el navegador envía a action y sigue _redirect.
  3. Úsalo en una página

    app/pages/contact.vue
    <template>
      <ContactForm />
    </template>

La alternativa necesita renderizado en servidor o prerenderizado. Con ssr: false, la página está vacía hasta que se ejecuta JavaScript, así que sin él no hay formulario que enviar.

Dominios permitidos

Incluye producción y los hosts de vista previa de donde despliegues.

Dónde funciona el formularioAñadir a Allowed domains
nuxt devlocalhost. Coincide con http://localhost:3000: Ikiform solo compara el nombre de host, nunca el puerto.
Tu dominioexample.com, que también cubre www.example.com
Vercelyour-project.vercel.app. Las URL de vista previa como your-project-git-branch-team.vercel.app no coinciden con él; añade el host exacto de la vista previa mientras la pruebas.
Netlifyyour-site.netlify.app. Los deploy previews (deploy-preview-12--your-site.netlify.app) y los branch deploys (branch--your-site.netlify.app) no son subdominios suyos, así que añade el host exacto de la vista previa mientras la pruebas.
Cloudflare Pagesyour-project.pages.dev. Las URL de vista previa como abc123.your-project.pages.dev son subdominios suyos, así que ya coinciden.
  • Nunca añadas vercel.app, netlify.app ni pages.dev solos. Coincidiría cualquier sitio de ese host, incluidos los de otras personas.

Lista de pruebas

  1. Ejecuta nuxt dev con localhost en los dominios permitidos y envía. El mensaje de agradecimiento debería sustituir al formulario.
  2. Desactiva JavaScript en las herramientas de desarrollo del navegador, recarga y envía. Deberías llegar a la página de agradecimiento de Ikiform en local, o a /thanks en tu dominio https:.
  3. Envía un email no válido con campos definidos y confirma que el mensaje aparece debajo del campo.
  4. Ejecuta nuxt generate, despliega el resultado y envía desde el host de vista previa.

Solución de problemas

Qué vesPor quéSolución
FetchError: [POST] "https://ikiform.com/f/...": 422Falta ignoreResponseError, así que $fetch lanza excepciones ante erroresAñade ignoreResponseError: true.
404 not_found, y el action termina en /f/undefinedEl id del formulario no estaba definido cuando se generó el sitioDefine NUXT_PUBLIC_IKIFORM_FORM_ID en el entorno de build, o el valor por defecto en nuxt.config.ts.
Sin JavaScript no pasa nadaLa página se renderiza en el cliente con ssr: falseActiva el renderizado en servidor o prerenderiza la página.
Los envíos llegan al filtro de spam con origin_mismatchLa página que envió no está entre los dominios permitidos del formularioAñade el host de la tabla de arriba. Si eliges Reject, esos mismos envíos reciben 403 origin_not_allowed.