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

Publicado em 2026-10-25 · https://ikiform.com/pt-br/guides/contact-form-static-site/vercel

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:

`Terminal`:

```bash
curl -i -X POST -d "message=hello" https://your-project.vercel.app/contact
```

`Resposta`:

```http
HTTP/2 405
server: Vercel
content-disposition: inline
x-vercel-cache: HIT
content-length: 0
```

A 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 |

Diagrama: Como a Vercel decide o que fazer com um POST de formulário

1. O formulário envia para um caminho no seu deploy da Vercel.
2. A Vercel procura uma função que trate esse caminho.
3. Se você escreveu uma, ela roda. Num build estático não existe nenhuma, então a Vercel responde 405 com corpo vazio.
4. Com o action apontando para o Ikiform, o envio nunca chega à Vercel.
5. 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.

A Vercel não está com defeito aqui. Um deploy estático não tem código para um POST, e uma função é o único jeito de adicionar um do lado 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:

`Antes`:

```html
<form method="post">
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <button>Send</button>
</form>
```

`Depois`:

```html
<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 `required` do 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](https://ikiform.com/blog/form-submissions-to-your-own-server.md) 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:

`Variáveis de ambiente da Vercel`:

```text
VITE_IKIFORM_FORM_ID   Production   frm_xxxxxxxxxxxxxxxx
VITE_IKIFORM_FORM_ID   Preview      frm_yyyyyyyyyyyyyyyy
```

O 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:

`index.html`:

```html
<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 com `400 Bad Request` antes 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.

`src/contact.js`:

```js
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](https://ikiform.com/pt-br/guides/contact-form-static-site.md#form) 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](https://ikiform.com/pt-br/docs/guides/nextjs.md#server-action).

## 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:

`vercel.json`:

```json
{
  "$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.

> Com o Vercel Authentication ligado e o escopo Standard Protection, que cobre todos os deploys exceto os domínios de produção, as URLs de preview ficam atrás de um login da Vercel. Só a sua equipe consegue acessá-las, então um formulário de preview sem domínios permitidos não fica exposto ao público. Confira **Settings › Deployment Protection** antes de contar com isso. Envios a partir de previews não são testes de `localhost`, então contam no seu plano como qualquer envio real.

## Confira se funciona

1. Rode o `curl` acima na sua URL de produção antes da correção e confirme o `405`. Depois da correção, o `action` do formulário no HTML publicado começa com `https://ikiform.com/f/`.
2. Abra um deploy de preview, envie e confirme que a mensagem chega no formulário de preview, não no de produção.
3. Envie no seu domínio de produção e confira se o campo `build` diz `production`.
4. 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`.
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

| 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

### Por que recebo 405 Method Not Allowed ao enviar um formulário na Vercel?

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.

### Posso usar uma Server Action do Next.js num export estático?

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.

### Os deploys de preview devem usar o mesmo formulário da produção?

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.

### Preciso do vercel.json para um formulário de contato?

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.

### O Ikiform é gratuito?

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.

## Mais guias

- [Formulário de contato em site estático](https://ikiform.com/pt-br/guides/contact-form-static-site.md): Coloque um formulário de contato funcionando num site estático sem servidor: para onde vai o POST, spam, respostas por e-mail e código para todo host e framework.
- [Netlify](https://ikiform.com/pt-br/guides/contact-form-static-site/netlify.md): 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.
- [Cloudflare Pages](https://ikiform.com/pt-br/guides/contact-form-static-site/cloudflare-pages.md): Adicione um formulário de contato ao Cloudflare Pages sem escrever uma Function: envie para um backend de formulários, defina headers no _headers e use suas próprias chaves do Turnstile.
- [GitHub Pages](https://ikiform.com/pt-br/guides/contact-form-static-site/github-pages.md): O GitHub Pages não roda código de servidor, então um POST de formulário recebe 405 Not Allowed; envie para um backend de formulários e acerte os redirecionamentos de project sites.
- [Amazon S3](https://ikiform.com/pt-br/guides/contact-form-static-site/aws-s3.md): Endpoints de site do S3 só aceitam GET e HEAD via HTTP simples, então envie o formulário de contato para um backend de formulários e acerte os redirecionamentos com o CloudFront.
- [Site de portfólio](https://ikiform.com/pt-br/guides/contact-form-static-site/portfolio-website.md): Um formulário de contato para portfólio que faz as perguntas de uma primeira conversa: projeto, orçamento, prazo e um link, sem servidor e com respostas pela sua caixa de entrada.
