Formulaire de contact sur un site statique

Formulaire de contact sur un site statique7 min de lecture

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.

Preet SutharFondateur, Ikiform

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
curl -i -X POST -d "message=hello" https://your-project.vercel.app/contact
Réponse
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 codePourquoi il renvoie 405 sur Vercel
<form method="post"> sans actionLe formulaire envoie vers la page où il se trouve, qui est un fichier statique
action="/api/contact" dans un projet Vite ou HTML simpleIl 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 statiqueoutput: "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
Comment Vercel décide quoi faire d'un POST de formulaireaction vers votre site Vercelaction vers IkiformPOST/contactdepuis leformulaireroutageVercelfichier oufonction ?unefonctionici ?votrefonctionsi vous en avezécrit une405, corpsvideun fichierstatiquePOST versikiform.commêmeformulaireIkiformstocke, vousécritpage deremerciementun fichierstatique surVercelouinon303Comment Vercel décide quoi faire d'un POST de formulaireaction vers votre site Vercelaction vers IkiformPOST /contactdepuis leformulaireroutage Vercelfichier oufonction ?unefonctionici ?votre fonctionsi vous en avezécrit une405,corpsvideun fichierstatiquePOST versikiform.commême formulaireIkiformstocke, vous écritpage deremerciementun fichier statiquesur Verceloui303non
  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
<form method="post">
  <input name="email" type="email" required />
  <textarea name="message" required></textarea>
  <button>Send</button>
</form>
Après
<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 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
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
<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
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 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.

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
{
  "$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èmeQue faire
Toujours 405 après avoir modifié le formulaireLe 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'IkiformL'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 spamVotre 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

Une étape vous bloque, ou vous voulez un guide pour votre cas ? Écrivez à hello@ikiform.com.