ID del formulario

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.

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

  1. Añade variables de entorno públicas

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

  2. Prerenderiza la página

    src/routes/contact/+page.js
    export const prerender = true;
  3. Añade el formulario

    src/routes/contact/+page.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
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 formularioAñadir a Allowed domains
vite devlocalhost. Coincide con http://localhost:5173: 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.
Cloudflare Pagesyour-project.pages.dev. Las URL de vista previa como abc123.your-project.pages.dev son subdominios suyos, así que ya coinciden.
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.
  • 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é vesPor quéSolución
El build falla diciendo que $env/static/public no exporta PUBLIC_IKIFORM_FORM_IDLa variable no está definida donde se ejecuta el buildAñá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-actionuse:enhance en un formulario que envía fuera de tu appQuítalo y quédate con el handler onsubmit.
El navegador se niega a enviar el formulario por form-action o connect-srckit.csp no incluye IkiformAñade https://ikiform.com a ambas directivas.
Sin JavaScript llegas a la página de agradecimiento de IkiformPUBLIC_SITE_URL es http:, o el dominio no está en los dominios permitidosUsa tu dirección https: y añade el dominio.
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.