# SvelteKit

> Una página prerenderizada de SvelteKit cuyo formulario envía directamente a Ikiform, mejorada con fetch cuando carga JavaScript. Funciona con adapter-static.

Esta página no necesita servidor: el `action` del formulario es tu endpoint de Ikiform, así que funciona con `adapter-static` y en cualquier hosting estático. Sin JavaScript, envía y redirige como HTML normal; con JavaScript, envía con `fetch` y muestra el resultado en su sitio. Para enviar mediante una form action con una clave secreta, consulta [Svelte](https://ikiform.com/es/docs/guides/svelte.md#sveltekit).

## Por qué no usar use:enhance

El `use:enhance` de SvelteKit solo funciona con formularios `method="POST"` que apuntan a actions definidas en un archivo `+page.server.js`. Ikiform es una URL externa, así que la página usa un pequeño handler `onsubmit`. La mejora progresiva es la misma: primero funciona el formulario y el script lo mejora.

## Configúralo

### Paso 1: Añade variables de entorno públicas

`.env`:

```bash
PUBLIC_IKIFORM_FORM_ID=frm_xxxxxxxxxxxxxxxx
PUBLIC_SITE_URL=https://example.com
```

`PUBLIC_SITE_URL` es para la redirección sin JavaScript. Durante el prerenderizado, `page.url.origin` es `http://sveltekit-prerender`, no tu dominio, así que la redirección no se puede construir a partir de él.

### Paso 2: Prerenderiza la página

`src/routes/contact/+page.js`:

```js
export const prerender = true;
```

### Paso 3: Añade el formulario

`src/routes/contact/+page.svelte`:

```svelte
<script>
  import { PUBLIC_IKIFORM_FORM_ID, PUBLIC_SITE_URL } from "$env/static/public";

  const endpoint = `https://ikiform.com/f/${PUBLIC_IKIFORM_FORM_ID}`;

  let status = $state("idle");
  let errors = $state({});
  let message = $state("");

  async function onsubmit(event) {
    event.preventDefault();
    status = "sending";
    try {
      const response = await fetch(endpoint, {
        method: "POST",
        headers: { Accept: "application/json" },
        body: new FormData(event.currentTarget),
      });
      const result = await response.json();
      if (result.ok) {
        status = "sent";
        return;
      }
      errors = result.error.fields ?? {};
      message = result.error.message;
    } catch {
      message = "Couldn't reach the server. Try again.";
    }
    status = "error";
  }
</script>

{#if status === "sent"}
  <p>Thanks, we got it.</p>
{:else}
  <form action={endpoint} method="POST" {onsubmit}>
    <label>
      Email
      <input name="email" type="email" autocomplete="email" required aria-invalid={errors.email ? "true" : undefined} />
    </label>
    {#if errors.email}<p>{errors.email[0]}</p>{/if}
    <label>
      Message
      <textarea name="message" rows="5"></textarea>
    </label>
    <input type="hidden" name="_redirect" value={`${PUBLIC_SITE_URL}/contact/thanks`} />
    <input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
    {#if status === "error" && !errors.email}<p role="alert">{message}</p>{/if}
    <button disabled={status === "sending"}>{status === "sending" ? "Sending" : "Send"}</button>
  </form>
{/if}
```

Añade un `src/routes/contact/thanks/+page.svelte` como destino de la redirección.

## Si usas la CSP de SvelteKit

Con `kit.csp` configurado, permite Ikiform tanto para el envío con `fetch` como para el envío normal del formulario. Si no, el navegador bloquea uno de los dos.

`svelte.config.js`:

```js
const config = {
  kit: {
    csp: {
      directives: {
        "connect-src": ["self", "https://ikiform.com"],
        "form-action": ["self", "https://ikiform.com"],
      },
    },
  },
};

export default config;
```

## Dominios permitidos

Incluye producción y tus hosts de vista previa.

| Dónde funciona el formulario | Añadir a Allowed domains |
| --- | --- |
| `vite dev` | `localhost`. Coincide con `http://localhost:5173`: 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. |
| Cloudflare Pages | `your-project.pages.dev`. Las URL de vista previa como `abc123.your-project.pages.dev` son subdominios suyos, así que ya coinciden. |
| 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. |

- Nunca añadas `vercel.app`, `pages.dev` ni `netlify.app` solos. Coincidiría cualquier sitio de ese host, incluidos los de otras personas.

## Lista de pruebas

1. Ejecuta el servidor de desarrollo con `localhost` en los dominios permitidos y envía. El mensaje de agradecimiento debería sustituir al formulario.
2. Ejecuta `vite build` y comprueba que el resultado contiene `contact.html` con el formulario ya dentro.
3. Desactiva JavaScript, recarga y envía. En tu dominio `https:` deberías llegar a `/contact/thanks`.
4. Envía desde un despliegue de vista previa y confirma que no se marca como spam.

## Solución de problemas

| Qué ves | Por qué | Solución |
| --- | --- | --- |
| El build falla diciendo que `$env/static/public` no exporta `PUBLIC_IKIFORM_FORM_ID` | La variable no está definida donde se ejecuta el build | Añádela a `.env` en local y a los ajustes de build de tu hosting. |
| Al enviar no pasa nada y la consola muestra un error de CORS sobre la cabecera `x-sveltekit-action` | `use:enhance` en un formulario que envía fuera de tu app | Quítalo y quédate con el handler `onsubmit`. |
| El navegador se niega a enviar el formulario por `form-action` o `connect-src` | `kit.csp` no incluye Ikiform | Añade `https://ikiform.com` a ambas directivas. |
| Sin JavaScript llegas a la página de agradecimiento de Ikiform | `PUBLIC_SITE_URL` es `http:`, o el dominio no está en los dominios permitidos | Usa tu dirección `https:` y añade el dominio. |
| 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

- [Svelte](https://ikiform.com/es/docs/guides/svelte.md): Un componente de Svelte 5 y una server action.
- [Astro](https://ikiform.com/es/docs/guides/astro.md): Un formulario sin JavaScript, opcionalmente mejorado.
- [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.
