Formulário de contato em site estático7 min de leitura
Corrija o erro 405 num formulário de contato hospedado na Vercel
Uma página estática na Vercel responde a um POST de formulário com 405 Method Not Allowed; veja por quê e como enviar o formulário para um backend.
A Vercel serve o seu build estático como arquivos, e um POST para um arquivo recebe 405 com corpo vazio porque nenhuma função trata esse caminho. Aponte o action do formulário para o seu endpoint do Ikiform, ou escreva uma Vercel Function que aceite o envio. Com o Ikiform, defina o id do formulário por ambiente da Vercel, para que os previews enviem a um formulário de teste e a produção ao formulário real.
Por que a Vercel responde 405
A Vercel direciona cada requisição a um arquivo estático ou a uma função. Um site estático, um build do Vite ou um app Next.js com output: "export" só tem arquivos. Um GET para /contact devolve o arquivo; um POST para o mesmo caminho não tem para onde ir, então a Vercel o recusa antes que qualquer código seu possa rodar:
curl -i -X POST -d "message=hello" https://your-project.vercel.app/contactHTTP/2 405
server: Vercel
content-disposition: inline
x-vercel-cache: HIT
content-length: 0A resposta não tem corpo, então o navegador mostra uma página em branco ou a própria tela de erro. Três configurações levam a isso:
| Seu código | Por que dá 405 na Vercel |
|---|---|
<form method="post"> sem action | O formulário envia para a própria página em que está, que é um arquivo estático |
action="/api/contact" num projeto Vite ou de HTML puro | Não há arquivo numa pasta api/ na raiz do projeto, então não existe função para esse caminho |
| Uma Server Action ou um Route Handler do Next.js num export estático | output: "export" não suporta Server Actions nem Route Handlers que leem a requisição. O next dev acusa o erro, e o build não tem servidor para rodá-los |
- O formulário envia para um caminho no seu deploy da Vercel.
- A Vercel procura uma função que trate esse caminho.
- Se você escreveu uma, ela roda. Num build estático não existe nenhuma, então a Vercel responde 405 com corpo vazio.
- Com o action apontando para o Ikiform, o envio nunca chega à Vercel.
- O Ikiform armazena a mensagem, manda um e-mail para você e redireciona o navegador de volta para uma página estática no seu site da Vercel.
Então ou você adiciona uma função ou envia o post para outro lugar. Uma Vercel Function em api/contact.ts funciona, mas aí você precisa de uma API de e-mail, filtro de spam e limites de taxa, e a manutenção é sua. Enviar o post ao Ikiform mantém o deploy estático. A correção no HTML é um atributo:
<form method="post">
<input name="email" type="email" required />
<textarea name="message" required></textarea>
<button>Send</button>
</form><form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">Se você prefere escrever a função
Uma função é uma escolha justa quando a mensagem precisa ir para um lugar que só o seu código alcança. Some o que ela exige antes de começar:
- Uma conta num provedor de e-mail, um domínio de envio verificado e uma chave de API nas variáveis de ambiente da Vercel.
- Validação no servidor, porque os atributos
requireddo navegador são uma conveniência, não uma verificação. - Filtro de spam e limites de taxa, ou um formulário de contato que manda para você todos os bots da internet.
- Um lugar para guardar as mensagens quando o provedor de e-mail estiver fora do ar, e um jeito de reenviá-las.
Um backend de formulários cobre essa lista por você. Se ainda quiser seu próprio código no meio, deixe o formulário enviando ao Ikiform e receba um webhook assinado numa função; seu próprio servidor mostra o handler.
Um id de formulário por ambiente da Vercel
Todo pull request na Vercel ganha um deploy de preview. Se os previews enviam para o seu formulário de produção, toda mensagem de teste cai na sua caixa de entrada junto com as reais. Crie um segundo formulário no Ikiform para os previews e deixe as variáveis de ambiente da Vercel escolherem. Em Settings › Environment Variables, adicione o mesmo nome duas vezes:
VITE_IKIFORM_FORM_ID Production frm_xxxxxxxxxxxxxxxx
VITE_IKIFORM_FORM_ID Preview frm_yyyyyyyyyyyyyyyyO Vite substitui os placeholders %NAME% no index.html por variáveis VITE_ no build, então um formulário HTML simples pode usá-las sem JavaScript. VITE_VERCEL_ENV é definida pela Vercel em projetos Vite e registra de qual deploy veio uma mensagem:
<form id="contact" action="https://ikiform.com/f/%VITE_IKIFORM_FORM_ID%" method="post">
<label for="name">Name</label>
<input id="name" name="name" autocomplete="name" required />
<label for="email">Email</label>
<input id="email" name="email" type="email" autocomplete="email" required />
<label for="message">Message</label>
<textarea id="message" name="message" rows="6" minlength="10" required></textarea>
<input type="hidden" name="build" value="%VITE_VERCEL_ENV%" />
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
<button type="submit">Send message</button>
</form>- A Vercel lê as variáveis de ambiente no build de um site estático. Depois de mudar uma, faça o deploy de novo, ou o valor antigo continua no HTML.
- O Vite deixa o placeholder como está quando a variável não existe, então um id ausente vai para produção como
%VITE_IKIFORM_FORM_ID%e todo envio falha com400 Bad Requestantes de chegar ao formulário. A verificação abaixo pega isso. - O id do formulário é público de qualquer jeito. Só variáveis
VITE_chegam ao navegador, e é por isso que nenhum segredo deve ficar numa delas.
const form = document.querySelector("#contact");
if (form && form.action.includes("%VITE_")) {
console.error("VITE_IKIFORM_FORM_ID is not set for this Vercel environment");
form.querySelector("button").disabled = true;
}Usa Next.js com output: "export"? Leia process.env.NEXT_PUBLIC_IKIFORM_FORM_ID num client component; o guia principal tem um formulário para export estático. Se o seu app Next.js roda nos servidores da Vercel, uma Server Action pode enviar com uma chave secreta: veja o guia de Next.js.
Headers e URLs limpas
Se o seu site define uma Content-Security-Policy, defina-a no vercel.json e inclua o Ikiform. cleanUrls serve thanks.html em /thanks, o que dá um destino de redirecionamento mais limpo:
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"cleanUrls": true,
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "Content-Security-Policy",
"value": "default-src 'self'; form-action 'self' https://ikiform.com; connect-src 'self' https://ikiform.com"
}
]
}
]
}Previews e domínios permitidos
URLs de preview como your-project-git-branch-team.vercel.app não são subdomínios de your-project.vercel.app, então não correspondem a ele em Allowed domains. Os envios a partir delas caem no spam com origin_mismatch. É mais um motivo para ter um formulário de preview separado: deixe os domínios permitidos dele vazios, ou adicione o host de preview que você está testando.
localhost, então contam no seu plano como qualquer envio real.Confira se funciona
- Rode o
curlacima na sua URL de produção antes da correção e confirme o405. Depois da correção, oactiondo formulário no HTML publicado começa comhttps://ikiform.com/f/. - Abra um deploy de preview, envie e confirme que a mensagem chega no formulário de preview, não no de produção.
- Envie no seu domínio de produção e confira se o campo
builddizproduction. - 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. - 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
| Problema | O que fazer |
|---|---|
Ainda dá 405 depois de mudar o formulário | O deploy que você está testando foi gerado antes da mudança, ou um handler JavaScript envia para o seu próprio caminho com fetch. Procure /api/contact nos arquivos gerados. |
400 Bad Request em todo envio, ou 404 not_found do Ikiform | O id do formulário não chegou ao build, ou é o id errado para aquele ambiente. Confira se a variável existe para aquele ambiente e faça o deploy de novo. |
| Os previews funcionam, mas os envios em produção caem no spam | Seu domínio de produção não está em Allowed domains do formulário de produção. Adicione o domínio personalizado, além de your-project.vercel.app. |
| O navegador cai na página de agradecimento do Ikiform em vez da sua | O _redirect é ignorado, a menos que o domínio dele esteja em Allowed domains e ele seja uma URL https:. Adicione o domínio, ou defina Redirect after submit nas configurações do formulário. |
Perguntas
O formulário envia para um caminho que só tem um arquivo estático. A Vercel serve arquivos para requisições GET e, sem uma função para esse caminho, recusa o POST com 405. Envie o formulário para um backend de formulários ou adicione uma Vercel Function.
Não. Server Actions não são suportadas com output: export, porque não existe servidor para rodá-las. Envie a partir de um client component para um backend de formulários, ou faça o deploy sem o export estático para a Vercel rodar a action.
Melhor não. Dê aos previews um id de formulário próprio por meio de uma variável de ambiente Preview, para que mensagens de teste não cheguem à sua caixa de entrada nem aos seus webhooks.
Não. O formulário funciona sem ele. Adicione o vercel.json só para definir uma Content-Security-Policy que permita o ikiform.com, ou para ligar o cleanUrls e ter uma URL de agradecimento mais limpa.
Não, não há plano gratuito. Testar é grátis: você pode enviar 50 envios de teste por mês a partir de localhost antes de pagar. Para entrar no ar, o Pro começa em US$ 6/mês para 500 envios, ou você pode pagar uma vez 10.000 envios por US$ 49 ou 50.000 envios por US$ 149, com créditos que nunca expiram.
Travou em algum passo ou quer um guia para o seu caso? Envie um e-mail para hello@ikiform.com.