# 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](https://ikiform.com/es/docs/guides/vue.md#nuxt).

## Configúralo

### Paso 1: Pon el id del formulario en la runtime config

`nuxt.config.ts`:

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

### Paso 2: Añade el componente

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

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

### Paso 3: Úsalo en una página

`app/pages/contact.vue`:

```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 formulario | Añadir a Allowed domains |
| --- | --- |
| `nuxt dev` | `localhost`. Coincide con `http://localhost:3000`: Ikiform solo compara el nombre de host, nunca el puerto. |
| Tu dominio | `example.com`, que también cubre `www.example.com` |
| Vercel | `your-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. |
| Netlify | `your-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 Pages | `your-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é ves | Por qué | Solución |
| --- | --- | --- |
| `FetchError: [POST] "https://ikiform.com/f/...": 422` | Falta `ignoreResponseError`, así que `$fetch` lanza excepciones ante errores | Añade `ignoreResponseError: true`. |
| `404 not_found`, y el action termina en `/f/undefined` | El id del formulario no estaba definido cuando se generó el sitio | Define `NUXT_PUBLIC_IKIFORM_FORM_ID` en el entorno de build, o el valor por defecto en `nuxt.config.ts`. |
| Sin JavaScript no pasa nada | La página se renderiza en el cliente con `ssr: false` | Activa el renderizado en servidor o prerenderiza la página. |
| Los envíos llegan al filtro de spam con `origin_mismatch` | La página que envió no está entre los dominios permitidos del formulario | Añade el host de la tabla de arriba. Si eliges **Reject**, esos mismos envíos reciben `403 origin_not_allowed`. |

## Relacionado

- [Vue](https://ikiform.com/es/docs/guides/vue.md): Un componente de archivo único.
- [JavaScript puro](https://ikiform.com/es/docs/guides/javascript.md): `fetch` con errores en línea.
- [Dominios permitidos](https://ikiform.com/es/docs/spam.md#allowed-domains): Cómo se comparan los orígenes y qué pasa cuando no coinciden.
- [Errores](https://ikiform.com/es/docs/errors.md): Cada código de error, con su causa y su solución.
