Formulário de contato em site estático

Formulário de contato em site estático7 min de leitura

Adicione um formulário de contato na Netlify sem o Netlify Forms

Rode um formulário de contato num site da Netlify sem o Netlify Forms: sem detecção no build, envios JSON a partir do React e um formulário separado para deploy previews.

Preet SutharFundador, Ikiform

Deixe o data-netlify fora do formulário e aponte o action dele para o seu endpoint do Ikiform; assim a Netlify serve a página e nunca mexe no envio. Formulários em React e Vue não precisam de uma cópia HTML oculta e podem enviar JSON. Defina um id de formulário diferente para os deploy previews no netlify.toml, porque os hosts de preview não correspondem ao seu site em Allowed domains.

O que muda na Netlify

A Netlify é o único grande host estático com um recurso de formulários embutido. O Netlify Forms lê o HTML publicado depois de cada build, encontra os formulários marcados com data-netlify ou netlify e aceita envios para o seu próprio site nesses formulários. Todo outro envio para uma página estática falha, e na Netlify ele falha com 404, não 405:

Terminal
curl -i -X POST -d "message=hello" https://your-site.netlify.app/contact/
Resposta
HTTP/2 404
server: Netlify
cache-status: "Netlify Edge"; fwd=method; fwd-status=404
content-length: 0

Esse 404 faz as pessoas caçarem uma página faltando que não está faltando. Ele significa que nenhum formulário detectado correspondeu ao envio. Usar ou não o Netlify Forms é outra questão, e a comparação entre Ikiform e Netlify Forms responde com honestidade: se o seu site vai ficar para sempre na Netlify e o formulário é HTML simples, o Netlify Forms dá menos trabalho. Esta página é para os outros casos.

Sua situaçãoPor que o Netlify Forms atrapalha
O formulário é renderizado por React, Vue ou SvelteO build analisa HTML estático, então um formulário renderizado no cliente precisa de uma cópia oculta para a Netlify encontrar
Você quer enviar JSON com fetchO Netlify Forms só aceita corpos urlencoded e multipart
O site pode mudar de hostO formulário envia para o seu próprio domínio, então só funciona enquanto a Netlify servir esse domínio
As mensagens devem chegar a um webhook que você verificaO Ikiform assina cada entrega com headers do Standard Webhooks e tenta de novo quando falha
Netlify Forms e Ikiform lado a lado no mesmo site da Netlifycom Netlify Formscom IkiformbuildNetlify analisa oHTMLdata-netlify?POST para o seusiteNetlify armazena404 no POSTformulário ignoradobuildsem análisePOST paraikiform.comHTML ou JSONIkiformarmazena, envia e-mail,webhooksencontradonão encontradoNetlify Forms e Ikiform lado a lado no mesmo site da Netlifycom Netlify Formscom IkiformbuildNetlify analisa oHTMLdata-netlify?POST para o seusiteNetlify armazena404 noPOSTformulárioignoradobuildsem análisePOST paraikiform.comHTML ou JSONIkiformarmazena, enviae-mail, webhooksencontradonão encontrado
  1. Com o Netlify Forms, a Netlify analisa o HTML de cada deploy em busca de formulários marcados com data-netlify.
  2. Um formulário encontrado aceita envios para o seu próprio site, e a Netlify os armazena.
  3. Um formulário não encontrado, como um renderizado por JavaScript sem cópia oculta, recebe 404 em todo envio.
  4. Com o Ikiform, nada precisa ser encontrado no build.
  5. O formulário envia direto para ikiform.com, como HTML ou JSON, e o Ikiform armazena e manda um e-mail para você.
Os dois podem conviver num site. Um formulário com data-netlify vai para a Netlify; um formulário cujo action é um endpoint do Ikiform vai para o Ikiform.

Tire um formulário do Netlify Forms

  • Remova data-netlify ou netlify, netlify-honeypot e data-netlify-recaptcha do formulário, e qualquer input oculto form-name. Se ficar, o form-name é armazenado como um campo comum.
  • Apague a cópia estática oculta de um formulário JavaScript, como a de baixo. A Netlify só precisava dela para detectar o formulário.
  • Defina o action, ou a URL do fetch, como o seu endpoint do Ikiform. O antigo action="/thanks" vai para a configuração Redirect after submit do formulário.
  • Baixe antes os envios antigos na página Forms da Netlify. Eles não migram sozinhos.
index.html, apague isto
<form name="contact" netlify hidden>
  <input name="name" />
  <input name="email" />
  <textarea name="message"></textarea>
</form>

Deixe o form detection ligado se outros formulários do site ainda usam o Netlify Forms. A documentação da Netlify diz que desligar a detecção é só para sites que não usam o Netlify Forms. Quando nada mais o usar, desligá-lo economiza uma análise a cada deploy.

O que substitui as configurações de spam da Netlify

A Netlify roda o Akismet nos envios e oferece netlify-honeypot e reCAPTCHA. No Ikiform, o honeypot continua funcionando se você definir o Honeypot field do formulário com o nome que já usa, como bot-field. A pontuação de spam roda em todo envio sem configuração, e o Spam check definido como Ikiform troca o reCAPTCHA pelo Cloudflare Turnstile, sem chaves. Remova a div do reCAPTCHA quando fizer a troca.

Um formulário React que envia JSON

Sem o Netlify Forms no caminho, um formulário renderizado no cliente não precisa de cópia e pode enviar JSON, o que mantém os tipos intactos e permite limpar os valores antes. Este também registra qual contexto da Netlify gerou a página:

src/ContactForm.tsx
import { type FormEvent, useState } from "react";

const ENDPOINT = `https://ikiform.com/f/${import.meta.env.VITE_IKIFORM_FORM_ID}`;

interface Message {
  name: string;
  email: string;
  message: string;
  context: string;
}

export function ContactForm() {
  const [sent, setSent] = useState(false);
  const [error, setError] = useState("");

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const data = new FormData(event.currentTarget);
    const body: Message & Record<string, string> = {
      name: String(data.get("name") ?? "").trim(),
      email: String(data.get("email") ?? "").trim(),
      message: String(data.get("message") ?? "").trim(),
      context: import.meta.env.VITE_CONTEXT ?? "local",
      _gotcha: String(data.get("_gotcha") ?? ""),
    };
    setError("");
    const response = await fetch(ENDPOINT, {
      method: "POST",
      headers: { "Content-Type": "application/json", Accept: "application/json" },
      body: JSON.stringify(body),
    }).catch(() => null);
    const result = await response?.json().catch(() => null);
    if (result?.ok) return setSent(true);
    const fields: Record<string, string[]> = result?.error?.fields ?? {};
    setError(Object.values(fields)[0]?.[0] ?? result?.error?.message ?? "Couldn't send. Try again.");
  }

  if (sent) return <p role="status">Got it. I'll answer from my own inbox, usually the same day.</p>;

  return (
    <form onSubmit={onSubmit}>
      <label htmlFor="name">Name</label>
      <input id="name" name="name" autoComplete="name" required />
      <label htmlFor="email">Email</label>
      <input id="email" name="email" type="email" autoComplete="email" required />
      <label htmlFor="message">Message</label>
      <textarea id="message" name="message" rows={6} minLength={10} required />
      <input name="_gotcha" type="text" tabIndex={-1} autoComplete="off" hidden />
      {error ? <p role="alert">{error}</p> : null}
      <button>Send</button>
    </form>
  );
}
  • Content-Type: application/json diz ao Ikiform para interpretar JSON; Accept: application/json pede uma resposta em JSON.
  • VITE_CONTEXT é um nome de exemplo. A Netlify define CONTEXT durante o build; exponha-a ao Vite no seu comando de build, ou remova o campo.
  • O honeypot é enviado como uma chave JSON. Mantenha-o no corpo mesmo com o input oculto, para que os bots que o preenchem continuem se denunciando.

Os deploy previews ganham um formulário próprio

Os deploy previews ficam em hosts como deploy-preview-12--your-site.netlify.app. Isso não é um subdomínio de your-site.netlify.app, então não corresponde em Allowed domains, e os envios de preview caem no spam. Dê aos previews e aos branch deploys um formulário próprio no netlify.toml, e defina o header Content-Security-Policy no mesmo arquivo:

netlify.toml
[build]
  command = "npm run build"
  publish = "dist"

[context.production.environment]
  VITE_IKIFORM_FORM_ID = "frm_xxxxxxxxxxxxxxxx"

[context.deploy-preview.environment]
  VITE_IKIFORM_FORM_ID = "frm_yyyyyyyyyyyyyyyy"

[context.branch-deploy.environment]
  VITE_IKIFORM_FORM_ID = "frm_yyyyyyyyyyyyyyyy"

[[headers]]
  for = "/*"
  [headers.values]
    Content-Security-Policy = "default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com"

Deixe Allowed domains do formulário de preview vazio, ou adicione o host de preview exato enquanto testa. O formulário de produção fica com o seu domínio e your-site.netlify.app. Os headers no netlify.toml valem para todos os contextos de deploy; se você preferir um arquivo _headers, ele precisa acabar no diretório de publicação.

Nunca adicione netlify.app sozinho em Allowed domains. Todo site da Netlify passaria a corresponder, inclusive cópias do seu formulário em sites de outras pessoas.

Confira se funciona

  1. Abra um deploy preview, envie e confirme que a mensagem cai no formulário de preview.
  2. Faça o merge e envie em produção. Confirme que a mensagem cai no formulário de produção e que a página Forms da Netlify não mostra nada novo.
  3. Confira a caixa de entrada de cada destinatário das notificações. Cada um recebe um e-mail com os campos, e Reply vai para quem enviou quando o formulário tem um campo email.
  4. Envie com o honeypot preenchido, _gotcha=bot. Ele ainda recebe uma resposta de sucesso, mas o envio é armazenado como spam e não dispara e-mail nem webhook.
  5. Não chegou nada? Mude o filtro de status do painel para Spam. Spam recebe a mesma resposta de sucesso de um envio real.

Pegadinhas

ProblemaO que fazer
404 ao enviarO envio foi para o seu próprio site. Um build em cache pode ainda ter o action antigo, ou uma chamada fetch ainda envia para /. Procure form-name no diretório de publicação.
Os envios ainda aparecem na NetlifyAlgum formulário da página ainda envia para o seu próprio site com data-netlify ou netlify, então a Netlify continua detectando e armazenando. Remova o atributo, defina o action como o Ikiform e faça o deploy de novo.
404 not_found do Ikiform só nos previewsO contexto de deploy preview não tem VITE_IKIFORM_FORM_ID, então a URL termina em undefined. Adicione-a em [context.deploy-preview.environment].
403 origin_not_allowed, ou todo envio cai no spamO domínio da página não está em Allowed domains do formulário. Adicione-o, mais localhost para testes locais. Subdomínios de um domínio listado são permitidos.
422 files_not_supportedO formulário tem um <input type="file"> com um arquivo escolhido. Upload de arquivos ainda não é suportado; remova o input ou peça um link.

Perguntas

Travou em algum passo ou quer um guia para o seu caso? Envie um e-mail para hello@ikiform.com.