# Corriger l'erreur 405 d'un formulaire de contact hébergé sur Vercel

> Une page statique sur Vercel répond à un POST de formulaire par 405 Method Not Allowed ; voici pourquoi, et comment envoyer le formulaire à un backend.

Publié le 2026-10-25 · https://ikiform.com/fr/guides/contact-form-static-site/vercel

Vercel sert votre build statique sous forme de fichiers, et un `POST` vers un fichier reçoit `405` avec un corps vide, car aucune fonction ne gère ce chemin. Faites plutôt pointer l'`action` du formulaire vers votre endpoint Ikiform, ou écrivez une Vercel Function qui accepte l'envoi. Avec Ikiform, définissez l'id du formulaire par environnement Vercel, pour que les préproductions envoient vers un formulaire de test et la production vers le vrai.

## Pourquoi Vercel répond 405

Vercel achemine chaque requête vers un fichier statique ou une fonction. Un site statique, un build Vite ou une app Next.js avec `output: "export"` n'a que des fichiers. Un `GET` sur `/contact` renvoie le fichier ; un `POST` vers le même chemin n'a nulle part où aller, Vercel le refuse donc avant que votre code ne puisse s'exécuter :

`Terminal`:

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

`Réponse`:

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

La réponse n'a pas de corps : le navigateur affiche une page blanche ou son propre écran d'erreur. Trois configurations mènent ici :

| Votre code | Pourquoi il renvoie 405 sur Vercel |
| --- | --- |
| `<form method="post">` sans `action` | Le formulaire envoie vers la page où il se trouve, qui est un fichier statique |
| `action="/api/contact"` dans un projet Vite ou HTML simple | Il n'y a aucun fichier dans un dossier `api/` à la racine du projet, donc aucune fonction pour ce chemin |
| Une Server Action ou un Route Handler Next.js dans un export statique | `output: "export"` ne prend pas en charge les Server Actions, ni les Route Handlers qui lisent la requête. `next dev` signale l'erreur, et le build n'a pas de serveur pour les exécuter |

Schéma : Comment Vercel décide quoi faire d'un POST de formulaire

1. Le formulaire envoie vers un chemin de votre déploiement Vercel.
2. Vercel cherche une fonction qui gère ce chemin.
3. Si vous en avez écrit une, elle s'exécute. Dans un build statique, il n'y en a pas, Vercel répond donc 405 avec un corps vide.
4. Avec l'action pointant vers Ikiform, l'envoi n'atteint jamais Vercel.
5. Ikiform stocke le message, vous l'envoie par e-mail et redirige le navigateur vers une page statique de votre site Vercel.

Vercel n'est pas en panne. Un déploiement statique n'a pas de code pour un POST, et une fonction est le seul moyen d'en ajouter côté Vercel.

Vous ajoutez donc une fonction, ou vous envoyez ailleurs. Une Vercel Function dans `api/contact.ts` fonctionne, mais il vous faut alors une API d'e-mail, un filtrage du spam et des limites de débit, et c'est vous qui les maintenez. Envoyer vers Ikiform garde le déploiement statique. La correction dans le HTML tient en un attribut :

`Avant`:

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

`Après`:

```html
<form action="https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx" method="post">
```

### Si vous préférez écrire la fonction

Une fonction est un choix raisonnable quand le message doit aller là où seul votre code peut accéder. Faites le compte de ce qu'elle demande avant de vous lancer :

- Un compte chez un fournisseur d'e-mail, un domaine d'envoi vérifié et une clé d'API dans les variables d'environnement de Vercel.
- Une validation côté serveur, car les attributs `required` du navigateur sont une commodité, pas une vérification.
- Un filtrage du spam et des limites de débit, sinon un formulaire de contact qui vous transmet chaque bot d'Internet.
- Un endroit où garder les messages quand le fournisseur d'e-mail est en panne, et un moyen de les renvoyer.

Un backend de formulaires couvre cette liste pour vous. Si vous voulez quand même votre code dans la boucle, laissez le formulaire envoyer à Ikiform et recevez un webhook signé dans une fonction ; [votre propre serveur](https://ikiform.com/blog/form-submissions-to-your-own-server.md) montre le gestionnaire.

## Un id de formulaire par environnement Vercel

Chaque pull request sur Vercel a un déploiement de préproduction. Si les préproductions envoient vers votre formulaire de production, chaque message de test arrive dans votre boîte à côté des vrais. Créez un second formulaire Ikiform pour les préproductions, et laissez les variables d'environnement de Vercel choisir. Dans **Settings › Environment Variables**, ajoutez deux fois le même nom :

`Variables d'environnement Vercel`:

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

Vite remplace au build les placeholders `%NAME%` d'`index.html` par les variables `VITE_` : un formulaire HTML simple peut donc les utiliser sans JavaScript. `VITE_VERCEL_ENV` est défini par Vercel pour les projets Vite, et enregistre de quel déploiement vient un message :

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

- Pour un site statique, Vercel lit les variables d'environnement au build. Après en avoir changé une, redéployez, sinon l'ancienne valeur reste dans le HTML.
- Vite laisse un placeholder tel quel quand la variable n'existe pas : un id manquant est donc livré sous la forme `%VITE_IKIFORM_FORM_ID%` et chaque envoi échoue avec `400 Bad Request` avant d'atteindre le formulaire. La garde ci-dessous détecte ce cas.
- L'id du formulaire est public dans tous les cas. Seules les variables `VITE_` atteignent le navigateur, c'est pourquoi aucun secret ne doit s'y trouver.

`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;
}
```

Vous utilisez Next.js avec `output: "export"` ? Lisez plutôt `process.env.NEXT_PUBLIC_IKIFORM_FORM_ID` dans un composant client ; le [guide principal](https://ikiform.com/fr/guides/contact-form-static-site.md#form) contient un formulaire en export statique. Si votre app Next.js tourne bien sur les serveurs de Vercel, une Server Action peut envoyer avec une clé secrète : voir le [guide Next.js](https://ikiform.com/fr/docs/guides/nextjs.md#server-action).

## En-têtes et URL propres

Si votre site définit une Content-Security-Policy, définissez-la dans `vercel.json` en incluant Ikiform. `cleanUrls` sert `thanks.html` à l'adresse `/thanks`, ce qui donne une cible de redirection plus propre :

`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"
        }
      ]
    }
  ]
}
```

## Préproductions et domaines autorisés

Les URL de préproduction comme `your-project-git-branch-team.vercel.app` ne sont pas des sous-domaines de `your-project.vercel.app`, elles ne correspondent donc pas dans **Allowed domains**. Leurs envois finissent en spam avec `origin_mismatch`. Raison de plus pour un formulaire de préproduction séparé : laissez ses domaines autorisés vides, ou ajoutez l'hôte de préproduction que vous testez.

> Avec Vercel Authentication activé et la portée Standard Protection, qui couvre tous les déploiements sauf les domaines de production, les URL de préproduction sont derrière une connexion Vercel. Seule votre équipe peut y accéder : un formulaire de préproduction sans domaines autorisés n'est donc pas exposé au public. Vérifiez **Settings › Deployment Protection** avant de compter là-dessus. Les envois depuis les préproductions ne sont pas des tests `localhost` : ils comptent dans votre offre comme toute soumission réelle.

## Vérifiez que tout fonctionne

1. Lancez le `curl` ci-dessus sur votre URL de production avant la correction, et constatez le `405`. Après la correction, l'`action` du formulaire dans le HTML déployé commence par `https://ikiform.com/f/`.
2. Ouvrez un déploiement de préproduction, envoyez, et vérifiez que le message arrive dans le formulaire de préproduction, pas dans celui de production.
3. Envoyez depuis votre domaine de production et vérifiez que le champ `build` vaut `production`.
4. Vérifiez la boîte de réception de chaque destinataire des notifications. Chacun reçoit un e-mail avec les champs, et **Reply** répond à l'expéditeur quand le formulaire a un champ `email`.
5. Rien n'est arrivé ? Passez le filtre de statut du tableau de bord sur **Spam**. Le spam reçoit la même réponse de succès qu'une vraie soumission.

## Pièges

| Problème | Que faire |
| --- | --- |
| Toujours `405` après avoir modifié le formulaire | Le déploiement que vous testez a été construit avant la modification, ou un gestionnaire JavaScript envoie vers votre propre chemin avec `fetch`. Cherchez `/api/contact` dans les fichiers compilés. |
| `400 Bad Request` à chaque envoi, ou `404 not_found` de la part d'Ikiform | L'id du formulaire n'a pas atteint le build, ou ce n'est pas le bon id pour cet environnement. Vérifiez que la variable existe pour cet environnement et redéployez. |
| Les préproductions fonctionnent, les envois de production finissent en spam | Votre domaine de production manque dans les **Allowed domains** du formulaire de production. Ajoutez le domaine personnalisé en plus de `your-project.vercel.app`. |
| Le navigateur arrive sur la page de remerciement d'Ikiform au lieu de la vôtre | `_redirect` est ignoré sauf si son domaine figure dans **Allowed domains** et qu'il s'agit d'une URL `https:`. Ajoutez le domaine, ou définissez plutôt **Redirect after submit** dans les réglages du formulaire. |

## Questions

### Pourquoi ai-je une erreur 405 Method Not Allowed en envoyant un formulaire sur Vercel ?

Le formulaire envoie vers un chemin qui n'a qu'un fichier statique. Vercel sert les fichiers pour les requêtes GET, et sans fonction pour ce chemin, il refuse le POST avec 405. Envoyez le formulaire vers un backend de formulaires ou ajoutez une Vercel Function.

### Puis-je utiliser une Server Action Next.js dans un export statique ?

Non. Les Server Actions ne sont pas prises en charge avec output : export, car il n'y a pas de serveur pour les exécuter. Envoyez depuis un composant client vers un backend de formulaires, ou déployez sans export statique pour que Vercel exécute l'action.

### Les déploiements de préproduction doivent-ils utiliser le même formulaire que la production ?

Mieux vaut pas. Donnez aux préproductions leur propre id de formulaire via une variable d'environnement Preview, pour que les messages de test n'atteignent ni votre boîte ni vos webhooks.

### Ai-je besoin de vercel.json pour un formulaire de contact ?

Non. Le formulaire fonctionne sans. N'ajoutez vercel.json que pour définir une Content-Security-Policy qui autorise ikiform.com, ou pour activer cleanUrls et obtenir une URL de remerciement plus propre.

### Ikiform est-il gratuit ?

Non, il n'y a pas d'offre gratuite. Les tests sont gratuits : vous pouvez envoyer 50 soumissions de test par mois depuis localhost avant de payer. Pour passer en production, Pro commence à 6 $/mois pour 500 soumissions, ou vous pouvez payer une seule fois 10 000 soumissions pour 49 $ ou 50 000 soumissions pour 149 $, avec des crédits qui n'expirent jamais.

## Autres guides

- [Formulaire de contact sur un site statique](https://ikiform.com/fr/guides/contact-form-static-site.md): Ajoutez un formulaire de contact fonctionnel à un site statique sans serveur : où va le POST, le spam, les réponses par e-mail, et du code pour chaque hébergeur.
- [Netlify](https://ikiform.com/fr/guides/contact-form-static-site/netlify.md): Un formulaire de contact sur Netlify sans Netlify Forms : pas de détection au build, envois JSON depuis React, et un formulaire à part pour les deploy previews.
- [Cloudflare Pages](https://ikiform.com/fr/guides/contact-form-static-site/cloudflare-pages.md): Un formulaire de contact sur Cloudflare Pages sans écrire de Function : envoyez à un backend, définissez les en-têtes dans _headers, utilisez vos clés Turnstile.
- [GitHub Pages](https://ikiform.com/fr/guides/contact-form-static-site/github-pages.md): GitHub Pages n'exécute pas de code serveur, un POST de formulaire reçoit donc 405 Not Allowed ; envoyez vers un backend et réglez bien les redirections.
- [Amazon S3](https://ikiform.com/fr/guides/contact-form-static-site/aws-s3.md): Les website endpoints S3 n'acceptent que GET et HEAD en HTTP simple : envoyez le formulaire à un backend et gérez bien les redirections avec CloudFront.
- [Site portfolio](https://ikiform.com/fr/guides/contact-form-static-site/portfolio-website.md): Un formulaire de contact de portfolio qui pose les questions d'un premier appel : projet, budget, délai et un lien, sans serveur et avec réponses depuis votre boîte.
