ID do formulário

SvelteKit

Uma página SvelteKit pré-renderizada cujo formulário envia direto para o Ikiform, aprimorada com fetch quando o JavaScript carrega. Funciona com adapter-static.

Esta página não precisa de servidor: o action do formulário é o seu endpoint do Ikiform, então funciona com adapter-static e em qualquer host estático. Sem JavaScript, ela envia e redireciona como HTML puro; com JavaScript, envia com fetch e mostra o resultado no lugar. Para enviar por uma form action com uma chave secreta, veja Svelte.

Por que não usar use:enhance

O use:enhance do SvelteKit só funciona com formulários method="POST" que apontam para actions definidas em um arquivo +page.server.js. O Ikiform é uma URL externa, então a página usa um pequeno handler onsubmit. O progressive enhancement é o mesmo: o formulário funciona primeiro, e o script o melhora.

Configuração

  1. Adicione variáveis de ambiente públicas

    .env
    PUBLIC_IKIFORM_FORM_ID=frm_xxxxxxxxxxxxxxxx
    PUBLIC_SITE_URL=https://example.com

    PUBLIC_SITE_URL serve para o redirecionamento sem JavaScript. Durante a pré-renderização, page.url.origin é http://sveltekit-prerender, não o seu domínio, então o redirecionamento não pode ser montado a partir dele.

  2. Pré-renderize a página

    src/routes/contact/+page.js
    export const prerender = true;
  3. Adicione o formulário

    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}

    Adicione um src/routes/contact/thanks/+page.svelte como destino do redirecionamento.

Se você usa a CSP do SvelteKit

Com kit.csp configurado, permita o Ikiform tanto para o envio com fetch quanto para o envio do formulário comum. Caso contrário, o navegador bloqueia um ou outro.

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

export default config;

Domínios permitidos

Liste a produção mais os seus hosts de preview.

Onde o formulário rodaAdicione em Allowed domains
vite devlocalhost. Ele corresponde a http://localhost:5173: o Ikiform compara só o nome do host, nunca a porta.
Seu domínioexample.com, que também cobre www.example.com
Vercelyour-project.vercel.app. URLs de preview como your-project-git-branch-team.vercel.app não correspondem a ele; adicione o host exato do preview enquanto testa um.
Cloudflare Pagesyour-project.pages.dev. URLs de preview como abc123.your-project.pages.dev são subdomínios dele, então já correspondem.
Netlifyyour-site.netlify.app. Deploy previews (deploy-preview-12--your-site.netlify.app) e branch deploys (branch--your-site.netlify.app) não são subdomínios dele, então adicione o host exato do preview enquanto testa um.
  • Nunca adicione vercel.app, pages.dev ou netlify.app sozinhos. Todo site naquele host corresponderia, inclusive os de outras pessoas.

Checklist de teste

  1. Rode o servidor de desenvolvimento com localhost nos domínios permitidos e envie. A mensagem de agradecimento deve substituir o formulário.
  2. Rode vite build e confira se o resultado contém contact.html com o formulário já dentro.
  3. Desative o JavaScript, recarregue e envie. No seu domínio https:, você deve chegar a /contact/thanks.
  4. Envie a partir de um deploy de preview e confirme que ele não é marcado como spam.

Solução de problemas

O que você vêPor quêCorreção
O build falha dizendo que PUBLIC_IKIFORM_FORM_ID não é exportado por $env/static/publicA variável não está definida onde o build rodaAdicione em .env localmente e nas configurações de build do seu host.
Nada acontece ao enviar, e o console mostra um erro de CORS sobre o header x-sveltekit-actionuse:enhance em um formulário que envia para fora do seu appRemova-o e mantenha o handler onsubmit.
O navegador se recusa a enviar o formulário por causa de form-action ou connect-srckit.csp não inclui o IkiformAdicione https://ikiform.com às duas diretivas.
Sem JavaScript, você cai na página de agradecimento do IkiformPUBLIC_SITE_URL é http:, ou o domínio não está nos domínios permitidosUse o seu endereço https: e adicione o domínio.
Os envios chegam no filtro de spam com origin_mismatchA página que enviou não está nos domínios permitidos do formulárioAdicione o host da tabela acima. Com Reject escolhido, os mesmos envios recebem 403 origin_not_allowed.